From 5fc6e8f4b4766fed3af44623e964866d126d37d3 Mon Sep 17 00:00:00 2001 From: Matt Stivali Date: Wed, 22 Jul 2026 17:12:23 -0400 Subject: [PATCH] fix(material/form-field): restore aria-live on mat-error --- .bazelversion | 2 +- .../assistant-to-the-branch-manager.yml | 4 +- .github/workflows/ci.material-aio.yml | 22 +- .github/workflows/ci.yml | 50 +- .../workflows/deploy-dev-app-main-push.yml | 8 +- .github/workflows/dev-infra.yml | 21 +- .github/workflows/docs-preview-build.yml | 8 +- .github/workflows/docs-preview-deploy.yml | 4 +- .github/workflows/google-internal-tests.yml | 4 +- .github/workflows/pr.material-aio.yml | 22 +- .github/workflows/pr.yml | 48 +- .github/workflows/preview-build-dev-app.yml | 8 +- .github/workflows/preview-deploy-dev-app.yml | 4 +- .github/workflows/scheduled-ci.yml | 18 +- .github/workflows/scorecard.yml | 6 +- .npmrc | 12 + .nvmrc | 2 +- .pullapprove.yml | 3 + BUILD.bazel | 2 +- CHANGELOG.md | 553 +- MODULE.bazel | 60 +- MODULE.bazel.lock | 874 +- README.md | 5 - docs/.npmrc | 1 + docs/.nvmrc | 2 +- docs/BUILD.bazel | 2 +- docs/defs.bzl | 2 +- docs/package.json | 8 +- .../scenes/src/app/scene-overlay-container.ts | 4 +- docs/src/app/material-docs-app.ts | 9 +- .../component-category-list.ts | 3 +- .../component-sidenav-can-load-guard.ts | 4 +- .../component-sidenav/component-sidenav.ts | 11 +- .../component-viewer/component-styling.ts | 5 +- .../pages/component-viewer/token-table.html | 16 +- .../pages/component-viewer/token-table.scss | 2 +- .../app/pages/component-viewer/token-table.ts | 12 +- docs/src/app/pages/guide-list/guide-list.ts | 3 +- .../app/pages/guide-viewer/guide-viewer.ts | 3 +- docs/src/app/pages/page-title/page-title.ts | 4 +- docs/src/app/shared/analytics/analytics.ts | 42 +- .../shared/analytics/error-report-handler.ts | 4 +- docs/src/app/shared/carousel/carousel.spec.ts | 8 +- docs/src/app/shared/carousel/carousel.ts | 2 - .../app/shared/cookie-popup/cookie-popup.ts | 3 +- .../angular-aria-banner.ts | 3 +- .../shared/doc-viewer/deprecated-tooltip.ts | 3 +- .../app/shared/doc-viewer/doc-viewer.spec.ts | 4 +- docs/src/app/shared/doc-viewer/doc-viewer.ts | 6 +- .../documentation-items.ts | 17 +- .../app/shared/example-viewer/code-snippet.ts | 3 +- .../shared/example-viewer/example-viewer.ts | 2 - .../src/app/shared/guide-items/guide-items.ts | 4 +- docs/src/app/shared/header-tag-manager.ts | 4 +- docs/src/app/shared/navbar/navbar.ts | 3 +- .../navigation-focus.service.ts | 6 +- .../shared/stackblitz/stackblitz-button.ts | 3 +- .../shared/stackblitz/stackblitz-writer.ts | 4 +- .../app/shared/style-manager/style-manager.ts | 4 +- .../table-of-contents/table-of-contents.ts | 2 - .../app/shared/theme-picker/theme-picker.ts | 3 +- .../theme-storage/theme-storage.ts | 4 +- .../shared/version-picker/version-picker.ts | 3 +- .../src/assets/img/android-chrome-192x192.png | Bin 5690 -> 0 bytes .../src/assets/img/android-chrome-512x512.png | Bin 19472 -> 0 bytes docs/src/assets/img/apple-touch-icon.png | Bin 7046 -> 0 bytes docs/src/assets/img/favicon-16x16.png | Bin 676 -> 0 bytes docs/src/assets/img/favicon-32x32.png | Bin 1447 -> 0 bytes docs/src/assets/img/favicon-48x48.png | Bin 3300 -> 0 bytes docs/src/assets/img/favicon.ico | Bin 15086 -> 15406 bytes docs/src/assets/img/manifest.json | 9 + docs/src/assets/img/safari-pinned-tab.svg | 1 - docs/src/assets/img/site.webmanifest | 19 - docs/src/assets/stackblitz/package-lock.json | 3911 +++--- docs/src/assets/stackblitz/package.json | 30 +- docs/src/assets/stackblitz/tsconfig.json | 1 + docs/src/assets/tokens/BUILD.bazel | 2 +- docs/src/index.html | 17 +- docs/tsconfig.json | 21 +- goldens/aria/accordion/index.api.md | 32 +- goldens/aria/accordion/testing/index.api.md | 59 - goldens/aria/combobox/index.api.md | 84 +- goldens/aria/combobox/testing/index.api.md | 47 - goldens/aria/grid/index.api.md | 39 +- goldens/aria/grid/testing/index.api.md | 66 - goldens/aria/listbox/index.api.md | 51 +- goldens/aria/listbox/testing/index.api.md | 50 - goldens/aria/menu/index.api.md | 33 +- goldens/aria/menu/testing/index.api.md | 55 - goldens/aria/private/index.api.md | 560 +- goldens/aria/tabs/index.api.md | 32 +- goldens/aria/tabs/testing/index.api.md | 50 - goldens/aria/toolbar/index.api.md | 17 +- goldens/aria/toolbar/testing/index.api.md | 60 - goldens/aria/tree/index.api.md | 15 +- goldens/aria/tree/testing/index.api.md | 61 - goldens/cdk/a11y/index.api.md | 41 +- goldens/cdk/accordion/index.api.md | 7 +- goldens/cdk/bidi/index.api.md | 2 +- goldens/cdk/clipboard/index.api.md | 3 +- goldens/cdk/collections/index.api.md | 5 - goldens/cdk/dialog/index.api.md | 11 +- goldens/cdk/drag-drop/index.api.md | 18 +- goldens/cdk/layout/index.api.md | 3 +- goldens/cdk/menu/index.api.md | 8 +- goldens/cdk/observers/index.api.md | 2 + goldens/cdk/overlay/index.api.md | 33 +- goldens/cdk/platform/index.api.md | 1 + goldens/cdk/portal/index.api.md | 10 +- goldens/cdk/scrolling/index.api.md | 32 +- goldens/cdk/stepper/index.api.md | 16 +- goldens/cdk/table/index.api.md | 36 +- goldens/cdk/text-field/index.api.md | 4 +- goldens/cdk/tree/index.api.md | 11 +- goldens/google-maps/index.api.md | 59 +- goldens/material/autocomplete/index.api.md | 10 +- goldens/material/badge/index.api.md | 14 +- goldens/material/bottom-sheet/index.api.md | 10 +- goldens/material/button-toggle/index.api.md | 4 +- goldens/material/button/index.api.md | 16 +- goldens/material/button/testing/index.api.md | 1 - goldens/material/card/index.api.md | 2 +- goldens/material/checkbox/index.api.md | 4 +- goldens/material/chips/index.api.md | 17 +- goldens/material/core/index.api.md | 21 +- goldens/material/datepicker/index.api.md | 42 +- .../material/datepicker/testing/index.api.md | 14 - goldens/material/dialog/index.api.md | 10 +- goldens/material/dialog/testing/index.api.md | 4 +- goldens/material/expansion/index.api.md | 35 +- goldens/material/form-field/index.api.md | 3 +- goldens/material/grid-list/index.api.md | 3 + .../material/grid-list/testing/index.api.md | 2 +- goldens/material/icon/index.api.md | 2 +- goldens/material/input/index.api.md | 6 +- goldens/material/list/index.api.md | 16 +- goldens/material/menu/index.api.md | 8 +- goldens/material/paginator/index.api.md | 3 +- goldens/material/progress-bar/index.api.md | 2 +- .../material/progress-spinner/index.api.md | 2 +- goldens/material/radio/index.api.md | 4 +- goldens/material/select/index.api.md | 12 +- goldens/material/sidenav/index.api.md | 13 +- goldens/material/slide-toggle/index.api.md | 9 +- goldens/material/slider/index.api.md | 10 +- goldens/material/snack-bar/index.api.md | 4 +- goldens/material/sort/index.api.md | 15 +- goldens/material/stepper/index.api.md | 12 +- goldens/material/tabs/index.api.md | 34 +- goldens/material/toolbar/index.api.md | 1 + goldens/material/tooltip/index.api.md | 9 +- goldens/material/tree/index.api.md | 2 +- goldens/youtube-player/index.api.md | 16 +- guides/getting-started.md | 13 +- guides/material-2.md | 21 +- guides/theming-your-components.md | 2 +- guides/theming.md | 37 - integration/linker/link-packages-test.mjs | 6 +- integration/module-tests/find-all-modules.mts | 2 +- integration/ng-add-standalone/package.json | 1 - .../ng-add-standalone/pnpm-workspace.yaml | 6 - integration/ng-add-standalone/tsconfig.json | 1 + integration/ng-add/package.json | 1 - integration/ng-add/pnpm-workspace.yaml | 6 - integration/ng-add/tsconfig.json | 1 + integration/vitest/package.json | 21 +- integration/vitest/pnpm-lock.yaml | 2691 ++-- integration/vitest/pnpm-workspace.yaml | 7 - integration/yarn-pnp-compat/angular.json | 5 + integration/yarn-pnp-compat/package.json | 20 +- integration/yarn-pnp-compat/tsconfig.json | 1 + integration/yarn-pnp-compat/yarn.lock | 5879 +++------ package.json | 94 +- pkg-externals.bzl | 2 - pnpm-lock.yaml | 10488 +++++++++------- pnpm-workspace.yaml | 85 +- renovate.json | 2 +- scripts/approve-api-golden.mts | 4 +- scripts/circleci/setup-angular-snapshots.js | 10 +- scripts/create-legacy-tests-bundle.mjs | 4 +- scripts/create-package-archives.mjs | 33 +- scripts/docs-deploy/deploy-to-site.mts | 41 +- scripts/tsconfig.json | 2 +- src/BUILD.bazel | 2 +- src/aria/BUILD.bazel | 18 +- src/aria/accordion/BUILD.bazel | 5 - src/aria/accordion/accordion-content.ts | 3 +- src/aria/accordion/accordion-group.ts | 95 +- src/aria/accordion/accordion-panel.ts | 81 +- src/aria/accordion/accordion-trigger.ts | 93 +- src/aria/accordion/accordion.spec.ts | 890 +- src/aria/accordion/testing/BUILD.bazel | 42 - .../testing/accordion-harness-filters.ts | 22 - .../testing/accordion-harness.spec.ts | 150 - .../accordion/testing/accordion-harness.ts | 119 - src/aria/accordion/testing/index.ts | 9 - src/aria/accordion/testing/public-api.ts | 10 - src/aria/combobox/BUILD.bazel | 2 +- src/aria/combobox/combobox-dialog.ts | 84 + src/aria/combobox/combobox-input.ts | 90 + src/aria/combobox/combobox-popup-container.ts | 52 + src/aria/combobox/combobox-popup.ts | 85 +- src/aria/combobox/combobox-tokens.ts | 6 +- src/aria/combobox/combobox-widget.ts | 78 - src/aria/combobox/combobox.spec.ts | 1709 +-- src/aria/combobox/combobox.ts | 195 +- src/aria/combobox/combobox.zone.spec.ts | 100 - src/aria/combobox/public-api.ts | 4 +- src/aria/combobox/testing/BUILD.bazel | 43 - .../testing/combobox-harness-filters.ts | 19 - .../combobox/testing/combobox-harness.spec.ts | 200 - src/aria/combobox/testing/combobox-harness.ts | 151 - src/aria/combobox/testing/index.ts | 9 - src/aria/combobox/testing/public-api.ts | 10 - src/aria/config.bzl | 10 +- src/aria/grid/BUILD.bazel | 24 +- src/aria/grid/grid-cell-widget.ts | 29 +- src/aria/grid/grid-cell.ts | 44 +- src/aria/grid/grid-row.ts | 37 +- src/aria/grid/grid-tokens.ts | 4 - src/aria/grid/grid.spec.ts | 1173 -- src/aria/grid/grid.ts | 88 +- src/aria/grid/testing/BUILD.bazel | 42 - src/aria/grid/testing/grid-harness-filters.ts | 30 - src/aria/grid/testing/grid-harness.spec.ts | 130 - src/aria/grid/testing/grid-harness.ts | 165 - src/aria/grid/testing/index.ts | 9 - src/aria/grid/testing/public-api.ts | 10 - src/aria/listbox/BUILD.bazel | 6 +- src/aria/listbox/listbox.spec.ts | 791 +- src/aria/listbox/listbox.ts | 151 +- src/aria/listbox/option.ts | 38 +- src/aria/listbox/public-api.ts | 10 + src/aria/listbox/testing/BUILD.bazel | 42 - src/aria/listbox/testing/index.ts | 9 - .../testing/listbox-harness-filters.ts | 25 - .../listbox/testing/listbox-harness.spec.ts | 120 - src/aria/listbox/testing/listbox-harness.ts | 124 - src/aria/listbox/testing/public-api.ts | 10 - src/aria/menu/BUILD.bazel | 3 - src/aria/menu/menu-bar.ts | 34 +- src/aria/menu/menu-content.ts | 2 + src/aria/menu/menu-item.ts | 53 +- src/aria/menu/menu-trigger.ts | 12 +- src/aria/menu/menu.spec.ts | 1043 +- src/aria/menu/menu.ts | 101 +- src/aria/menu/testing/BUILD.bazel | 40 - src/aria/menu/testing/index.ts | 9 - src/aria/menu/testing/menu-harness-filters.ts | 25 - src/aria/menu/testing/menu-harness.spec.ts | 177 - src/aria/menu/testing/menu-harness.ts | 144 - src/aria/menu/testing/public-api.ts | 10 - src/aria/private/BUILD.bazel | 12 +- src/aria/private/accordion/accordion.spec.ts | 137 +- src/aria/private/accordion/accordion.ts | 121 +- .../event-manager/click-event-manager.ts | 92 - .../private/behaviors/event-manager/index.ts | 1 - .../event-manager/keyboard-event-manager.ts | 11 +- .../event-manager/pointer-event-manager.ts | 2 +- .../grid-focus}/BUILD.bazel | 21 +- .../behaviors/grid-focus/grid-focus.spec.ts | 443 + .../behaviors/grid-focus/grid-focus.ts | 223 + .../behaviors/grid-navigation/BUILD.bazel | 30 + .../grid-navigation/grid-navigation.spec.ts | 1253 ++ .../grid-navigation/grid-navigation.ts | 158 + .../private/behaviors/grid/grid-focus.spec.ts | 4 +- .../behaviors/grid/grid-navigation.spec.ts | 250 +- src/aria/private/behaviors/grid/grid.spec.ts | 6 +- src/aria/private/behaviors/grid/grid.ts | 43 +- .../behaviors/list-focus/list-focus.spec.ts | 4 +- .../behaviors/list-focus/list-focus.ts | 15 +- .../list-navigation/list-navigation.spec.ts | 36 +- .../list-selection/list-selection.spec.ts | 146 +- .../list-selection/list-selection.ts | 28 +- .../list-typeahead/list-typeahead.ts | 8 +- src/aria/private/behaviors/list/list.spec.ts | 84 +- src/aria/private/behaviors/list/list.ts | 23 +- .../behaviors/signal-like/signal-like.ts | 2 + src/aria/private/behaviors/tree/tree.spec.ts | 98 +- src/aria/private/behaviors/tree/tree.ts | 26 +- src/aria/private/combobox/BUILD.bazel | 1 - src/aria/private/combobox/combobox.spec.ts | 1127 +- src/aria/private/combobox/combobox.ts | 823 +- .../deferred-content/deferred-content.spec.ts | 15 +- .../deferred-content/deferred-content.ts | 20 +- src/aria/private/grid/BUILD.bazel | 1 - src/aria/private/grid/cell.ts | 190 +- src/aria/private/grid/grid.spec.ts | 406 +- src/aria/private/grid/grid.ts | 126 +- src/aria/private/grid/widget.ts | 91 +- src/aria/private/listbox/BUILD.bazel | 1 + src/aria/private/listbox/combobox-listbox.ts | 111 + src/aria/private/listbox/listbox.spec.ts | 447 +- src/aria/private/listbox/listbox.ts | 77 +- src/aria/private/listbox/option.ts | 22 +- src/aria/private/menu/menu.spec.ts | 140 +- src/aria/private/menu/menu.ts | 255 +- src/aria/private/public-api.ts | 9 +- src/aria/private/tabs/tabs.spec.ts | 86 +- src/aria/private/tabs/tabs.ts | 132 +- src/aria/private/testing/BUILD.bazel | 10 - src/aria/private/testing/test-helpers.ts | 15 - src/aria/private/toolbar/toolbar-widget.ts | 2 +- src/aria/private/toolbar/toolbar.spec.ts | 241 +- src/aria/private/toolbar/toolbar.ts | 33 +- src/aria/private/tree/BUILD.bazel | 2 + src/aria/private/tree/combobox-tree.ts | 120 + src/aria/private/tree/tree.spec.ts | 365 +- src/aria/private/tree/tree.ts | 70 +- src/aria/private/utils/collection.spec.ts | 133 - src/aria/private/utils/collection.ts | 65 - .../private/utils/element-resolver.spec.ts | 51 - src/aria/private/utils/element-resolver.ts | 28 - src/aria/private/utils/transforms.ts | 17 - src/aria/private/utils/violations.ts | 17 - src/aria/tabs/BUILD.bazel | 2 - src/aria/tabs/tab-content.ts | 2 + src/aria/tabs/tab-list.ts | 116 +- src/aria/tabs/tab-panel.ts | 47 +- src/aria/tabs/tab.ts | 63 +- src/aria/tabs/tabs.spec.ts | 640 +- src/aria/tabs/tabs.ts | 77 +- src/aria/tabs/testing/BUILD.bazel | 38 - src/aria/tabs/testing/index.ts | 9 - src/aria/tabs/testing/public-api.ts | 10 - src/aria/tabs/testing/tabs-harness-filters.ts | 22 - src/aria/tabs/testing/tabs-harness.spec.ts | 155 - src/aria/tabs/testing/tabs-harness.ts | 113 - src/aria/tabs/{tab-tokens.ts => utils.ts} | 15 +- src/aria/toolbar/BUILD.bazel | 1 - src/aria/toolbar/testing/BUILD.bazel | 39 - src/aria/toolbar/testing/index.ts | 9 - src/aria/toolbar/testing/public-api.ts | 12 - .../testing/toolbar-harness-filters.ts | 27 - .../toolbar/testing/toolbar-harness.spec.ts | 131 - src/aria/toolbar/testing/toolbar-harness.ts | 55 - .../testing/toolbar-widget-group-harness.ts | 36 - .../toolbar/testing/toolbar-widget-harness.ts | 66 - src/aria/toolbar/toolbar-widget-group.ts | 22 +- src/aria/toolbar/toolbar-widget.ts | 10 +- src/aria/toolbar/toolbar.spec.ts | 616 +- src/aria/toolbar/toolbar.ts | 68 +- .../toolbar/{toolbar-tokens.ts => utils.ts} | 10 + src/aria/tree/BUILD.bazel | 2 +- src/aria/tree/public-api.ts | 7 + src/aria/tree/testing/BUILD.bazel | 38 - src/aria/tree/testing/index.ts | 9 - src/aria/tree/testing/item-harness.ts | 102 - src/aria/tree/testing/public-api.ts | 11 - src/aria/tree/testing/tree-harness-filters.ts | 30 - src/aria/tree/testing/tree-harness.spec.ts | 234 - src/aria/tree/testing/tree-harness.ts | 102 - src/aria/tree/tree-item-group.ts | 5 +- src/aria/tree/tree-item.ts | 32 +- src/aria/tree/tree.spec.ts | 1251 +- src/aria/tree/tree.ts | 125 +- .../utils/element.ts => tree/utils.ts} | 0 .../coalesced-style-scheduler.ts | 7 +- .../column-resize/column-resize-notifier.ts | 6 +- .../column-resize/column-size-store.ts | 4 +- .../column-resize/event-dispatcher.ts | 4 +- .../column-resize/resize-strategy.ts | 8 +- .../popover-edit/edit-event-dispatcher.ts | 4 +- src/cdk-experimental/popover-edit/edit-ref.ts | 4 +- .../popover-edit/edit-services.ts | 4 +- .../popover-edit/focus-dispatcher.ts | 4 +- .../popover-edit/focus-escape-notifier.ts | 16 +- .../popover-edit/popover-edit.spec.ts | 322 +- .../scrolling/virtual-scroll-viewport.spec.ts | 3 +- .../selection/selection-column.ts | 2 + .../selection/selection.spec.ts | 124 +- .../table-scroll-container.spec.ts | 3 +- src/cdk/BUILD.bazel | 2 +- src/cdk/a11y/a11y.md | 9 +- .../aria-describer/aria-describer.spec.ts | 7 +- src/cdk/a11y/aria-describer/aria-describer.ts | 27 +- .../a11y/focus-monitor/focus-monitor.spec.ts | 256 +- src/cdk/a11y/focus-monitor/focus-monitor.ts | 9 +- .../focus-monitor/focus-monitor.zone.spec.ts | 16 +- .../configurable-focus-trap-factory.ts | 22 +- .../configurable-focus-trap.spec.ts | 2 - .../focus-trap/configurable-focus-trap.ts | 2 +- .../event-listener-inert-strategy.spec.ts | 29 +- src/cdk/a11y/focus-trap/focus-trap-manager.ts | 4 +- src/cdk/a11y/focus-trap/focus-trap.md | 2 +- src/cdk/a11y/focus-trap/focus-trap.spec.ts | 10 - src/cdk/a11y/focus-trap/focus-trap.ts | 19 +- .../high-contrast-mode-detector.ts | 6 +- src/cdk/a11y/id-generator.ts | 19 +- .../input-modality-detector.spec.ts | 8 +- .../input-modality/input-modality-detector.ts | 6 +- .../interactivity-checker.spec.ts | 20 +- .../interactivity-checker.ts | 7 +- .../a11y/key-manager/list-key-manager.spec.ts | 114 +- .../a11y/key-manager/tree-key-manager.spec.ts | 120 +- src/cdk/a11y/live-announcer/live-announcer.md | 10 +- .../live-announcer/live-announcer.spec.ts | 177 +- src/cdk/a11y/live-announcer/live-announcer.ts | 8 +- src/cdk/a11y/public-api.ts | 2 +- src/cdk/accordion/accordion-item.spec.ts | 5 +- src/cdk/accordion/accordion-item.ts | 3 + src/cdk/accordion/accordion.spec.ts | 10 +- src/cdk/accordion/accordion.ts | 4 +- src/cdk/bidi/directionality.spec.ts | 6 +- src/cdk/bidi/directionality.ts | 6 +- src/cdk/clipboard/clipboard.md | 6 +- src/cdk/clipboard/clipboard.ts | 7 +- src/cdk/clipboard/copy-to-clipboard.spec.ts | 3 +- src/cdk/clipboard/copy-to-clipboard.ts | 8 +- src/cdk/collections/selection-model.ts | 70 +- src/cdk/collections/selection.spec.ts | 53 - .../unique-selection-dispatcher.ts | 4 +- src/cdk/dialog/dialog-config.ts | 29 +- src/cdk/dialog/dialog-container.ts | 4 +- src/cdk/dialog/dialog.md | 26 +- src/cdk/dialog/dialog.spec.ts | 308 +- src/cdk/dialog/dialog.ts | 16 +- src/cdk/drag-drop/directives/drag-handle.ts | 2 + .../drag-drop/directives/drag-placeholder.ts | 2 + src/cdk/drag-drop/directives/drag-preview.ts | 2 + src/cdk/drag-drop/directives/drag.ts | 4 +- .../drag-drop/directives/drop-list-group.ts | 5 +- .../directives/drop-list-shared.spec.ts | 1011 +- src/cdk/drag-drop/directives/drop-list.ts | 2 + .../directives/mixed-drop-list.spec.ts | 29 +- .../directives/single-axis-drop-list.spec.ts | 59 +- .../directives/standalone-drag.spec.ts | 436 +- .../directives/standalone-drag.zone.spec.ts | 11 +- .../drag-drop/directives/test-utils.spec.ts | 8 +- src/cdk/drag-drop/drag-drop-registry.spec.ts | 9 +- src/cdk/drag-drop/drag-drop-registry.ts | 9 +- src/cdk/drag-drop/drag-drop.spec.ts | 9 +- src/cdk/drag-drop/drag-drop.ts | 7 +- src/cdk/drag-drop/drag-utils.spec.ts | 14 +- src/cdk/drag-drop/drop-list-ref.ts | 4 +- src/cdk/drag-drop/resets.scss | 1 - .../sorting/single-axis-sort-strategy.ts | 20 +- src/cdk/layout/breakpoints-observer.md | 12 +- src/cdk/layout/breakpoints-observer.spec.ts | 78 +- src/cdk/layout/breakpoints-observer.ts | 7 +- src/cdk/layout/layout.md | 5 +- src/cdk/layout/media-matcher.md | 18 +- src/cdk/layout/media-matcher.ts | 13 +- src/cdk/listbox/listbox.md | 2 +- src/cdk/listbox/listbox.spec.ts | 13 +- src/cdk/menu/context-menu-trigger.spec.ts | 15 +- src/cdk/menu/context-menu-trigger.ts | 6 + src/cdk/menu/menu-aim.ts | 4 +- src/cdk/menu/menu-bar.spec.ts | 24 +- src/cdk/menu/menu-group.spec.ts | 4 +- src/cdk/menu/menu-item-checkbox.spec.ts | 3 +- src/cdk/menu/menu-item-radio.spec.ts | 3 +- src/cdk/menu/menu-item.spec.ts | 8 +- src/cdk/menu/menu-stack.spec.ts | 9 +- src/cdk/menu/menu-stack.ts | 4 +- src/cdk/menu/menu-trigger-base.ts | 4 +- src/cdk/menu/menu-trigger.spec.ts | 24 +- src/cdk/menu/menu-trigger.ts | 2 +- src/cdk/menu/menu.md | 4 +- src/cdk/menu/menu.spec.ts | 110 +- src/cdk/menu/pointer-focus-tracker.spec.ts | 3 - src/cdk/observers/observe-content.spec.ts | 6 +- src/cdk/observers/observe-content.ts | 12 +- .../private/shared-resize-observer.spec.ts | 3 +- .../private/shared-resize-observer.ts | 6 +- .../dispatchers/base-overlay-dispatcher.ts | 8 +- .../overlay-keyboard-dispatcher.spec.ts | 8 +- .../overlay-keyboard-dispatcher.ts | 4 +- .../overlay-outside-click-dispatcher.spec.ts | 10 +- .../overlay-outside-click-dispatcher.ts | 4 +- .../fullscreen-overlay-container.spec.ts | 11 +- .../overlay/fullscreen-overlay-container.ts | 10 +- src/cdk/overlay/overlay-container.spec.ts | 10 +- src/cdk/overlay/overlay-container.ts | 16 +- src/cdk/overlay/overlay-directives.spec.ts | 30 +- src/cdk/overlay/overlay-directives.ts | 7 +- .../overlay/overlay-directives.zone.spec.ts | 10 +- src/cdk/overlay/overlay.md | 63 +- src/cdk/overlay/overlay.spec.ts | 125 +- src/cdk/overlay/overlay.ts | 13 +- ...exible-connected-position-strategy.spec.ts | 11 +- .../flexible-connected-position-strategy.ts | 6 +- .../position/global-position-strategy.spec.ts | 3 +- .../position/overlay-position-builder.ts | 7 +- .../scroll/block-scroll-strategy.spec.ts | 3 +- .../scroll/close-scroll-strategy.spec.ts | 9 +- .../scroll/close-scroll-strategy.zone.spec.ts | 15 +- .../scroll/reposition-scroll-strategy.spec.ts | 3 +- .../overlay/scroll/scroll-strategy-options.ts | 7 +- src/cdk/platform/features/shadow-dom.ts | 13 +- src/cdk/platform/platform.ts | 9 +- src/cdk/portal/dom-portal-outlet.ts | 4 - src/cdk/portal/portal-directives.ts | 10 +- src/cdk/portal/portal.spec.ts | 92 +- src/cdk/portal/portal.ts | 17 - src/cdk/private/style-loader.ts | 4 +- src/cdk/private/trusted-types.ts | 14 +- .../visually-hidden/visually-hidden.ts | 3 +- src/cdk/schematics/BUILD.bazel | 2 +- src/cdk/schematics/migration.json | 8 +- .../ng-generate/drag-drop/index.spec.ts | 16 +- src/cdk/schematics/ng-update/index.ts | 6 +- src/cdk/schematics/testing/test-case-setup.ts | 2 +- src/cdk/schematics/tsconfig.json | 1 + .../schematics/update-tool/target-version.ts | 2 +- src/cdk/scrolling/scroll-dispatcher.spec.ts | 12 +- src/cdk/scrolling/scroll-dispatcher.ts | 79 +- .../scrolling/scroll-dispatcher.zone.spec.ts | 9 +- src/cdk/scrolling/scrollable.spec.ts | 19 +- src/cdk/scrolling/scrollable.ts | 7 +- src/cdk/scrolling/viewport-ruler.spec.ts | 10 +- src/cdk/scrolling/viewport-ruler.ts | 6 +- src/cdk/scrolling/viewport-ruler.zone.spec.ts | 2 +- src/cdk/scrolling/virtual-for-of.ts | 2 + .../scrolling/virtual-scroll-viewport.scss | 6 +- .../scrolling/virtual-scroll-viewport.spec.ts | 679 +- src/cdk/scrolling/virtual-scroll-viewport.ts | 4 + .../virtual-scroll-viewport.zone.spec.ts | 35 +- .../scrolling/virtual-scrollable-element.ts | 6 + .../scrolling/virtual-scrollable-window.ts | 2 + src/cdk/scrolling/virtual-scrollable.ts | 5 + src/cdk/stepper/step-header.ts | 3 + src/cdk/stepper/step-label.ts | 3 + src/cdk/stepper/stepper-button.ts | 6 + src/cdk/stepper/stepper.ts | 56 +- src/cdk/table/BUILD.bazel | 2 + src/cdk/table/cell.ts | 18 + src/cdk/table/row.ts | 43 +- src/cdk/table/table.spec.ts | 191 +- src/cdk/table/table.ts | 15 +- src/cdk/table/text-column.spec.ts | 17 +- src/cdk/table/text-column.ts | 4 +- .../testing/private/fake-directionality.ts | 5 +- ...selenium-web-driver-harness-environment.ts | 17 +- src/cdk/testing/tests/test-main-component.ts | 2 + src/cdk/testing/tests/test-shadow-boundary.ts | 4 +- src/cdk/testing/tests/test-sub-component.ts | 3 +- src/cdk/text-field/autofill.spec.ts | 4 +- src/cdk/text-field/autofill.ts | 10 +- src/cdk/text-field/autofill.zone.spec.ts | 10 +- src/cdk/text-field/autosize.spec.ts | 5 +- src/cdk/text-field/autosize.ts | 2 + src/cdk/text-field/text-field-style-loader.ts | 3 +- src/cdk/tree/nested-node.ts | 6 + src/cdk/tree/node.ts | 3 + src/cdk/tree/outlet.ts | 3 + src/cdk/tree/padding.ts | 2 + src/cdk/tree/toggle.ts | 13 +- .../tree-using-legacy-key-manager.spec.ts | 10 +- src/cdk/tree/tree-with-tree-control.spec.ts | 36 +- src/cdk/tree/tree.spec.ts | 91 +- src/cdk/tree/tree.ts | 9 +- .../accordion-configurable-example.html | 92 +- .../accordion-configurable-example.ts | 52 +- .../accordion-disabled-focusable-example.html | 30 +- .../accordion-disabled-focusable-example.ts | 13 +- .../accordion-disabled-skipped-example.html | 30 +- .../accordion-disabled-skipped-example.ts | 13 +- .../accordion-disabled-example.html | 30 +- .../accordion-disabled-example.ts | 13 +- .../accordion-multi-expansion-example.html | 33 +- .../accordion-multi-expansion-example.ts | 13 +- .../accordion-single-expansion-example.html | 30 +- .../accordion-single-expansion-example.ts | 13 +- .../cdk-accordion-configurable-example.html | 109 + .../autocomplete-auto-select-example.html | 22 +- .../autocomplete-auto-select-example.ts | 67 +- .../autocomplete-disabled-example.html | 18 +- .../autocomplete-disabled-example.ts | 47 +- .../autocomplete-highlight-example.html | 45 +- .../autocomplete-highlight-example.ts | 70 +- .../autocomplete-manual-example.html | 41 +- .../autocomplete-manual-example.ts | 67 +- .../aria/autocomplete/autocomplete.css | 39 +- .../aria/autocomplete/index.ts | 4 +- .../aria/combobox/BUILD.bazel | 6 - .../combobox-auto-select-example.html | 52 +- .../combobox-auto-select-example.ts | 106 +- .../combobox-datepicker-example.css | 106 - .../combobox-datepicker-example.html | 68 - .../combobox-datepicker-example.ts | 187 - .../combobox-dialog-example.css | 222 + .../combobox-dialog-example.html | 84 +- .../combobox-dialog-example.ts | 127 +- .../combobox-disabled-example.html | 46 +- .../combobox-disabled-example.ts | 122 +- ...combobox-editable-multiselect-example.html | 52 - .../combobox-editable-multiselect-example.ts | 98 - .../aria/combobox/combobox-example.css | 379 - .../aria/combobox/combobox-examples.css | 189 + .../combobox-grid/combobox-grid-example.html | 43 - .../combobox-grid/combobox-grid-example.ts | 99 - .../combobox-hard-disabled-example.html | 27 - .../combobox-hard-disabled-example.ts | 52 - .../combobox-highlight-example.html | 57 +- .../combobox-highlight-example.ts | 139 +- .../combobox-listbox-example.html | 33 - .../combobox-listbox-example.ts | 46 - .../combobox-manual-example.html | 33 + .../combobox-manual-example.ts | 132 + .../combobox-multiselect-dialog-example.html | 52 - .../combobox-multiselect-dialog-example.ts | 98 - .../combobox-readonly-disabled-example.html | 43 +- .../combobox-readonly-disabled-example.ts | 82 +- ...combobox-readonly-multiselect-example.html | 42 +- .../combobox-readonly-multiselect-example.ts | 82 +- .../combobox-readonly-example.html | 36 +- .../combobox-readonly-example.ts | 81 +- .../combobox-select-example.html | 27 - .../combobox-select-example.ts | 51 - .../combobox-tree-auto-select-example.html | 93 +- .../combobox-tree-auto-select-example.ts | 169 +- .../combobox-tree-highlight-example.html | 95 +- .../combobox-tree-highlight-example.ts | 170 +- .../combobox-tree-manual-example.html | 63 + .../combobox-tree-manual-example.ts | 104 + .../combobox-tree/combobox-tree-example.html | 47 - .../combobox-tree/combobox-tree-example.ts | 100 - .../aria/combobox/countries.ts | 197 - src/components-examples/aria/combobox/data.ts | 23 + .../aria/combobox/index.ts | 19 +- ...select-example.css => select-examples.css} | 54 +- .../aria/combobox/states.ts | 60 - src/components-examples/aria/grid/BUILD.bazel | 2 - .../grid-calendar/grid-calendar-example.html | 2 - .../grid-calendar/grid-calendar-example.ts | 45 +- .../grid-configurable-example.html | 19 +- .../grid-configurable-example.ts | 1 + .../grid/grid-table/grid-table-example.css | 1 - .../grid/grid-table/grid-table-example.html | 88 +- .../grid/grid-table/grid-table-example.ts | 39 +- .../listbox-configurable-example.html | 2 +- src/components-examples/aria/menu/BUILD.bazel | 1 - src/components-examples/aria/menu/index.ts | 1 - .../menu-cdk-overlay-example.html | 43 - .../menu-cdk-overlay-example.ts | 27 - .../aria/menu/menu-example.css | 4 +- .../menubar-disabled-example.html | 12 - .../menubar-rtl/menubar-rtl-example.html | 12 - .../aria/menubar/menubar.css | 8 +- .../aria/menubar/menubar/menubar-example.html | 12 - .../aria/select/BUILD.bazel | 27 + src/components-examples/aria/select/index.ts | 3 + .../select-disabled-example.html | 28 + .../select-disabled-example.ts | 38 + .../select-multi/select-multi-example.html | 27 + .../select-multi/select-multi-example.ts | 74 + .../aria/select/select.css | 137 + .../aria/select/select/select-example.html | 35 + .../aria/select/select/select-example.ts | 66 + src/components-examples/aria/tabs/index.ts | 1 - .../scrollable/tabs-scrollable-example.css | 7 - .../scrollable/tabs-scrollable-example.html | 13 - .../scrollable/tabs-scrollable-example.ts | 21 - .../aria/toolbar/simple-toolbar.ts | 91 +- .../toolbar-basic-horizontal-example.ts | 4 +- .../aria/toolbar/toolbar-common.css | 10 - .../toolbar-configurable-example.ts | 4 +- .../toolbar-hard-disabled-example.ts | 4 +- .../toolbar-rtl/toolbar-rtl-example.ts | 4 +- .../tree-configurable-example.html | 2 +- .../cdk-dialog-data-example-dialog.css | 1 - .../cdk-dialog-overview-example-dialog.css | 1 - .../cdk-dialog-styling-example-dialog.css | 1 - .../cdk-menu-nested-context-example.css | 2 +- .../cdk-menu-standalone-menu-example.css | 12 - .../cdk-overlay-basic-example.css | 1 - .../cdk-overlay-basic-example.html | 6 +- .../cdk-portal-overview-example.css | 2 +- .../cdk-virtual-scroll-append-only-example.ts | 3 +- .../cdk-virtual-scroll-context-example.ts | 3 +- ...-virtual-scroll-custom-strategy-example.ts | 3 +- .../cdk-virtual-scroll-data-source-example.ts | 3 +- .../cdk-virtual-scroll-dl-example.ts | 3 +- ...cdk-virtual-scroll-fixed-buffer-example.ts | 3 +- .../cdk-virtual-scroll-horizontal-example.ts | 3 +- .../cdk-virtual-scroll-overview-example.ts | 3 +- ...virtual-scroll-parent-scrolling-example.ts | 3 +- ...k-virtual-scroll-template-cache-example.ts | 3 +- ...virtual-scroll-window-scrolling-example.ts | 3 +- .../cdk-tree-complex-example.ts | 3 +- .../cdk-tree-custom-key-manager-example.ts | 3 +- ...cdk-tree-flat-children-accessor-example.ts | 3 +- .../cdk-tree-flat-level-accessor-example.ts | 3 +- .../cdk-tree-flat/cdk-tree-flat-example.ts | 3 +- ...k-tree-nested-children-accessor-example.ts | 3 +- .../cdk-tree-nested-level-accessor-example.ts | 3 +- .../cdk-tree-nested-example.ts | 3 +- src/components-examples/config.bzl | 1 - ...lt-enabled-column-resize-flex-example.html | 2 +- ...default-enabled-column-resize-example.html | 2 +- .../opt-in-column-resize-example.html | 2 +- .../popover-edit-tab-out-mat-table-example.ts | 3 +- ...utocomplete-require-selection-example.html | 2 +- .../autocomplete-require-selection-example.ts | 10 +- .../badge-overview-example.html | 2 +- .../badge-overview/badge-overview-example.ts | 6 +- .../bottom-sheet-harness-example.ts | 6 +- .../button-toggle-appearance-example.ts | 3 +- .../button-toggle-forms-example.html | 2 +- .../button-toggle-forms-example.ts | 5 +- .../button-toggle-harness-example.ts | 3 +- .../button-toggle-mode-example.ts | 3 +- .../material/button/BUILD.bazel | 2 - .../button-harness-example.html | 2 +- .../button-harness-example.spec.ts | 4 +- .../button-harness/button-harness-example.ts | 4 +- .../button-progress-indicator-example.html | 22 - .../button-progress-indicator-example.ts | 19 - .../material/button/index.ts | 1 - .../card/card-actions/card-actions-example.ts | 3 +- .../card/card-footer/card-footer-example.ts | 3 +- .../card/card-harness/card-harness-example.ts | 3 +- .../card-media-size-example.ts | 3 +- .../card-overview/card-overview-example.ts | 3 +- .../checkbox-configurable-example.ts | 3 +- .../checkbox-harness-example.ts | 3 +- .../checkbox-overview-example.ts | 3 +- .../checkbox-reactive-forms-example.ts | 3 +- .../chips-autocomplete-example.ts | 3 +- .../chips-avatar/chips-avatar-example.ts | 3 +- .../chips-drag-drop-example.ts | 3 +- .../chips-form-control-example.ts | 3 +- .../chips-harness/chips-harness-example.ts | 3 +- .../chips/chips-input/chips-input-example.ts | 3 +- .../chips-overview/chips-overview-example.ts | 3 +- .../chips-reactive-form-example.html | 6 +- .../chips-reactive-form-example.ts | 44 +- .../chips-template-form-example.html | 8 +- .../chips-template-form-example.ts | 36 +- .../elevation-overview-example.html | 6 +- .../elevation-overview-example.ts | 4 +- .../ripple-overview-example.html | 13 +- .../ripple-overview-example.ts | 12 +- .../date-range-picker-comparison-example.ts | 3 +- .../date-range-picker-forms-example.ts | 3 +- .../date-range-picker-overview-example.ts | 3 +- ...range-picker-selection-strategy-example.ts | 5 +- .../datepicker-actions-example.ts | 3 +- .../datepicker-api/datepicker-api-example.ts | 3 +- .../datepicker-custom-header-example.ts | 4 +- .../datepicker-custom-icon-example.ts | 3 +- .../datepicker-date-class-example.ts | 3 +- .../datepicker-dialog-example.ts | 3 +- .../datepicker-disabled-example.ts | 3 +- .../datepicker-events-example.ts | 3 +- .../datepicker-filter-example.ts | 3 +- .../datepicker-formats-example.ts | 3 +- .../datepicker-harness-example.ts | 3 +- .../datepicker-inline-calendar-example.ts | 3 +- .../datepicker-locale-example.ts | 3 +- .../datepicker-luxon-example.ts | 3 +- .../datepicker-min-max-example.ts | 3 +- .../datepicker-overview-example.ts | 3 +- .../datepicker-start-view-example.ts | 3 +- .../datepicker-touch-example.ts | 3 +- .../datepicker-value-example.ts | 3 +- .../datepicker-views-selection-example.ts | 3 +- .../dialog-animations-example.ts | 4 +- .../dialog-content/dialog-content-example.ts | 4 +- .../dialog-elements-example.ts | 4 +- .../dialog-from-menu-example.ts | 4 +- .../dialog-harness/dialog-harness-example.ts | 3 +- .../dialog-overview-example.ts | 3 +- .../divider-harness-example.ts | 3 +- .../divider-overview-example.ts | 3 +- .../expansion-expand-collapse-all-example.ts | 3 +- .../expansion-harness-example.ts | 3 +- .../expansion-overview-example.ts | 3 +- .../expansion-steps-example.ts | 3 +- .../form-field-appearance-example.ts | 3 +- .../form-field-custom-control-example.ts | 3 + .../form-field-error-example.ts | 3 +- .../form-field-harness-example.ts | 3 +- .../form-field-hint-example.ts | 3 +- .../form-field-label-example.ts | 3 +- .../form-field-overview-example.ts | 3 +- .../form-field-prefix-suffix-example.ts | 3 +- .../icon/icon-harness/icon-harness-example.ts | 3 +- .../icon-overview/icon-overview-example.ts | 3 +- .../icon/icon-svg/icon-svg-example.ts | 3 +- .../input-clearable-example.html | 4 +- .../input-clearable-example.ts | 4 +- .../list-navigation-example.html | 8 +- .../list-navigation-example.ts | 4 +- .../paginator-configurable-example.html | 18 +- .../paginator-configurable-example.ts | 30 +- .../paginator-harness-example.html | 10 +- .../paginator-harness-example.spec.ts | 14 +- .../paginator-harness-example.ts | 18 +- .../paginator-intl/paginator-intl-example.ts | 4 +- .../progress-bar-configurable-example.html | 10 +- .../progress-bar-configurable-example.ts | 8 +- ...progress-spinner-configurable-example.html | 6 +- .../progress-spinner-configurable-example.ts | 6 +- .../radio-ng-model-example.html | 2 +- .../radio-ng-model/radio-ng-model-example.ts | 4 +- .../select-form/select-form-example.html | 4 +- .../select/select-form/select-form-example.ts | 6 +- .../select-initial-value-example.html | 6 +- .../select-initial-value-example.ts | 8 +- .../select-selectable-null-example.ts | 4 +- .../select-value-binding-example.html | 2 +- .../select-value-binding-example.ts | 4 +- .../sidenav-autosize-example.html | 4 +- .../sidenav-autosize-example.ts | 4 +- .../sidenav-disable-close-example.html | 2 +- .../sidenav-disable-close-example.ts | 10 +- .../sidenav-mode/sidenav-mode-example.ts | 2 +- .../sidenav-open-close-example.html | 6 +- .../sidenav-open-close-example.ts | 10 +- .../material/slide-toggle/index.ts | 1 - .../slide-toggle-configurable-example.html | 4 +- .../slide-toggle-configurable-example.ts | 6 +- .../slide-toggle-forms-example.html | 2 +- .../slide-toggle-forms-example.ts | 4 +- .../slide-toggle-full-width-example.css | 6 - .../slide-toggle-full-width-example.html | 4 - .../slide-toggle-full-width-example.ts | 13 - .../slide-toggle-harness-example.html | 2 +- .../slide-toggle-harness-example.spec.ts | 3 +- .../slide-toggle-harness-example.ts | 4 +- .../slider-configurable-example.html | 12 +- .../slider-configurable-example.ts | 16 +- .../snack-bar-annotated-component-example.ts | 6 +- .../snack-bar-component-example.ts | 6 +- .../snack-bar-position-example.ts | 10 +- .../sort-harness/sort-harness-example.html | 2 +- .../sort/sort-harness/sort-harness-example.ts | 16 +- .../sort-overview/sort-overview-example.html | 2 +- .../sort-overview/sort-overview-example.ts | 42 +- .../material/stepper/index.ts | 1 - .../stepper-editable-example.html | 8 +- .../stepper-editable-example.ts | 5 +- .../stepper-intl/stepper-intl-example.ts | 8 +- .../stepper-optional-example.html | 6 +- .../stepper-optional-example.ts | 5 +- .../stepper-overview-example.html | 6 +- .../stepper-overview-example.ts | 5 +- .../stepper-signal-forms-example.css | 7 - .../stepper-signal-forms-example.html | 38 - .../stepper-signal-forms-example.ts | 27 - .../stepper-vertical-example.html | 6 +- .../stepper-vertical-example.ts | 5 +- .../table-basic/table-basic-example.html | 2 +- .../table-column-styling-example.html | 2 +- .../table-drag-drop-example.html | 2 +- .../table-dynamic-array-data-example.html | 2 +- .../table-dynamic-columns-example.html | 2 +- ...table-dynamic-observable-data-example.html | 2 +- .../table-expandable-rows-example.html | 2 +- .../table-filtering-example.html | 2 +- .../table-flex-basic-example.html | 4 +- .../table-flex-large-row-example.html | 4 +- .../table-footer-row-example.html | 2 +- .../table-generated-columns-example.html | 2 +- .../table/table-http/table-http-example.css | 3 +- .../table/table-http/table-http-example.html | 28 +- .../table/table-http/table-http-example.ts | 103 +- .../table-multiple-header-footer-example.html | 2 +- .../table-multiple-row-template-example.html | 2 +- .../table-overview-example.html | 2 +- .../table-pagination-example.html | 2 +- .../table-recycle-rows-example.html | 2 +- .../table-row-binding-example.html | 2 +- .../table-row-context-example.html | 2 +- .../table-selection-example.html | 2 +- .../table-sorting/table-sorting-example.html | 2 +- .../table-sticky-columns-example.html | 2 +- .../table-sticky-complex-flex-example.html | 2 +- .../table-sticky-complex-example.html | 2 +- .../table-sticky-footer-example.html | 2 +- .../table-sticky-header-example.html | 2 +- .../table-text-column-advanced-example.html | 2 +- .../table-text-column-example.html | 2 +- .../table-with-ripples-example.html | 2 +- .../table/table-wrapped/wrapper-table.html | 2 +- .../tab-group-async-example.ts | 17 +- .../tab-group-drag-drop-example.html | 2 +- .../tab-group-drag-drop-example.ts | 12 +- .../tab-group-dynamic-height-example.html | 4 +- .../tab-group-dynamic-example.html | 4 +- .../tab-group-dynamic-example.ts | 10 +- .../tab-group-lazy-loaded-example.html | 16 +- .../tab-group-lazy-loaded-example.ts | 21 +- .../tab-group-stretched-example.html | 2 +- .../tab-nav-bar-basic-example.html | 6 +- .../tab-nav-bar-basic-example.ts | 8 +- .../timepicker-custom-icon-example.ts | 3 +- ...picker-datepicker-integration-example.html | 2 +- ...mepicker-datepicker-integration-example.ts | 5 +- .../timepicker-forms-example.ts | 3 +- .../timepicker-harness-example.ts | 3 +- .../timepicker-locale-example.ts | 5 +- .../timepicker-options-example.ts | 3 +- .../timepicker-overview-example.ts | 3 +- .../timepicker-validation-example.ts | 3 +- .../material/toolbar/index.ts | 1 - .../toolbar-navbar/toolbar-navbar-example.css | 3 - .../toolbar-navbar-example.html | 10 - .../toolbar-navbar/toolbar-navbar-example.ts | 14 - .../tooltip-delay/tooltip-delay-example.css | 5 +- .../tooltip-position-at-origin-example.css | 9 +- .../tree-dynamic/tree-dynamic-example.html | 17 +- .../tree/tree-dynamic/tree-dynamic-example.ts | 169 +- ...ee-flat-child-accessor-overview-example.ts | 3 +- .../tree-flat-overview-example.html | 4 +- .../tree-flat-overview-example.ts | 41 +- .../tree-harness/tree-harness-example.html | 15 +- .../tree/tree-harness/tree-harness-example.ts | 40 +- .../tree-legacy-keyboard-interface-example.ts | 3 +- .../tree-loadmore/tree-loadmore-example.html | 17 +- .../tree-loadmore/tree-loadmore-example.ts | 181 +- ...-nested-child-accessor-overview-example.ts | 3 +- .../tree-nested-overview-example.html | 17 +- .../tree-nested-overview-example.ts | 13 +- src/components-examples/package.json | 3 +- src/dev-app/BUILD.bazel | 1 + src/dev-app/aria-accordion/accordion-demo.ts | 3 +- .../aria-autocomplete/autocomplete-demo.css | 11 +- .../aria-autocomplete/autocomplete-demo.html | 36 +- .../aria-autocomplete/autocomplete-demo.ts | 3 +- src/dev-app/aria-combobox/combobox-demo.css | 3 - src/dev-app/aria-combobox/combobox-demo.html | 62 +- src/dev-app/aria-combobox/combobox-demo.ts | 29 +- src/dev-app/aria-grid/grid-demo.ts | 3 +- src/dev-app/aria-listbox/listbox-demo.ts | 3 +- src/dev-app/aria-menu/menu-demo.html | 5 - src/dev-app/aria-menu/menu-demo.ts | 5 +- src/dev-app/aria-menubar/menubar-demo.ts | 3 +- src/dev-app/aria-select/BUILD.bazel | 16 + src/dev-app/aria-select/select-demo.css | 9 + src/dev-app/aria-select/select-demo.html | 14 + src/dev-app/aria-select/select-demo.ts | 22 + src/dev-app/aria-tabs/tabs-demo.html | 5 - src/dev-app/aria-tabs/tabs-demo.ts | 5 +- src/dev-app/aria-toolbar/toolbar-demo.css | 2 - src/dev-app/aria-toolbar/toolbar-demo.html | 43 +- src/dev-app/aria-toolbar/toolbar-demo.ts | 3 +- src/dev-app/aria-tree/tree-demo.ts | 3 +- src/dev-app/autocomplete/autocomplete-demo.ts | 4 +- src/dev-app/badge/badge-demo.ts | 3 +- src/dev-app/baseline/baseline-demo.ts | 3 +- src/dev-app/bottom-sheet/bottom-sheet-demo.ts | 4 +- .../button-toggle/button-toggle-demo.ts | 3 +- src/dev-app/button/BUILD.bazel | 1 - src/dev-app/button/button-demo.html | 262 +- src/dev-app/button/button-demo.ts | 22 +- src/dev-app/card/card-demo.ts | 3 +- src/dev-app/cdk-dialog/dialog-demo.ts | 3 + src/dev-app/cdk-listbox/cdk-listbox-demo.ts | 3 +- src/dev-app/cdk-menu/cdk-menu-demo.ts | 3 +- src/dev-app/checkbox/checkbox-demo.ts | 4 +- src/dev-app/chips/chips-demo.ts | 3 +- src/dev-app/clipboard/clipboard-demo.ts | 3 +- .../column-resize/column-resize-home.ts | 3 +- .../connected-overlay-demo.ts | 2 + src/dev-app/datepicker/datepicker-demo.ts | 8 +- src/dev-app/dev-app.ts | 3 +- src/dev-app/dev-app/dev-app-404.ts | 3 +- src/dev-app/dev-app/dev-app-directionality.ts | 4 +- src/dev-app/dev-app/dev-app-home.ts | 3 +- src/dev-app/dev-app/dev-app-layout.ts | 5 +- src/dev-app/dev-app/ripple-options.ts | 4 +- src/dev-app/dialog/dialog-demo.ts | 5 + src/dev-app/drag-drop/drag-drop-demo.ts | 3 +- src/dev-app/drawer/drawer-demo.ts | 3 +- src/dev-app/example/example-list.ts | 3 +- src/dev-app/example/example.ts | 2 + src/dev-app/examples-page/examples-page.ts | 3 +- src/dev-app/expansion/expansion-demo.ts | 3 +- src/dev-app/focus-origin/focus-origin-demo.ts | 3 +- src/dev-app/focus-trap/focus-trap-demo.ts | 4 + src/dev-app/google-map/google-map-demo.html | 12 + src/dev-app/google-map/google-map-demo.ts | 31 +- src/dev-app/grid-list/grid-list-demo.ts | 3 +- src/dev-app/icon/icon-demo.ts | 3 +- .../input-modality-detector-demo.ts | 10 +- src/dev-app/input/input-demo.ts | 3 +- src/dev-app/layout/layout-demo.ts | 3 +- src/dev-app/list/list-demo.ts | 3 +- .../live-announcer/live-announcer-demo.ts | 3 +- src/dev-app/menu/menu-demo.ts | 3 +- src/dev-app/menubar/mat-menubar-demo.ts | 5 +- src/dev-app/paginator/paginator-demo.ts | 3 +- src/dev-app/performance/performance-demo.ts | 2 + src/dev-app/platform/platform-demo.ts | 3 +- src/dev-app/popover-edit/popover-edit-demo.ts | 3 +- src/dev-app/portal/portal-demo.ts | 11 +- src/dev-app/progress-bar/progress-bar-demo.ts | 3 +- .../progress-spinner/progress-spinner-demo.ts | 3 +- src/dev-app/radio/radio-demo.ts | 3 +- src/dev-app/ripple/ripple-demo.ts | 3 +- src/dev-app/routes.ts | 4 + src/dev-app/screen-type/screen-type-demo.ts | 3 +- src/dev-app/select/select-demo.ts | 3 +- src/dev-app/selection/selection-demo.ts | 3 +- src/dev-app/sidenav/sidenav-demo.ts | 3 +- src/dev-app/slide-toggle/BUILD.bazel | 1 - .../slide-toggle/slide-toggle-demo.html | 21 - .../slide-toggle/slide-toggle-demo.scss | 12 - src/dev-app/slide-toggle/slide-toggle-demo.ts | 6 +- src/dev-app/slider/slider-demo.ts | 4 +- src/dev-app/snack-bar/snack-bar-demo.ts | 10 +- src/dev-app/stepper/stepper-demo.ts | 3 +- .../system-classes-demo.ts | 3 +- src/dev-app/system/system-demo.ts | 3 +- .../table-scroll-container-demo.ts | 3 +- src/dev-app/table/table-demo.ts | 3 +- src/dev-app/tabs/tabs-demo.ts | 3 +- src/dev-app/theme/theme-demo.ts | 4 +- src/dev-app/timepicker/timepicker-demo.ts | 3 +- src/dev-app/toolbar/toolbar-demo.ts | 3 +- src/dev-app/tooltip/tooltip-demo.ts | 3 +- src/dev-app/tree/tree-demo.ts | 3 +- src/dev-app/typography/typography-demo.ts | 3 +- .../virtual-scroll/virtual-scroll-demo.ts | 3 +- .../youtube-player/youtube-player-demo.ts | 2 + .../block-scroll-strategy-e2e.ts | 3 +- .../components/component-harness-e2e.ts | 11 +- src/e2e-app/components/e2e-app/e2e-app.html | 6 +- src/e2e-app/components/e2e-app/e2e-app.ts | 5 +- src/e2e-app/components/home.ts | 3 +- src/e2e-app/components/slider-e2e.ts | 3 +- .../virtual-scroll/virtual-scroll-e2e.ts | 3 +- .../deprecated-map-marker-clusterer.spec.ts | 76 +- .../deprecated-map-marker-clusterer.ts | 7 +- src/google-maps/google-map/google-map.spec.ts | 3 +- src/google-maps/google-map/google-map.ts | 6 +- .../map-advanced-marker.spec.ts | 46 +- .../map-advanced-marker.ts | 32 +- src/google-maps/map-base-layer.ts | 3 + .../map-bicycling-layer.spec.ts | 10 +- src/google-maps/map-circle/map-circle.spec.ts | 35 +- src/google-maps/map-circle/map-circle.ts | 3 + .../map-directions-renderer/README.md | 20 +- .../map-directions-renderer.spec.ts | 30 +- .../map-directions-renderer.ts | 5 +- .../map-directions-service.ts | 12 +- src/google-maps/map-geocoder/README.md | 8 +- src/google-maps/map-geocoder/map-geocoder.ts | 9 +- .../map-ground-overlay.spec.ts | 36 +- .../map-ground-overlay/map-ground-overlay.ts | 3 + src/google-maps/map-heatmap-layer/README.md | 48 + .../map-heatmap-layer.spec.ts | 172 + .../map-heatmap-layer/map-heatmap-layer.ts | 170 +- .../map-info-window/map-info-window.spec.ts | 59 +- .../map-info-window/map-info-window.ts | 3 + .../map-kml-layer/map-kml-layer.spec.ts | 35 +- .../map-kml-layer/map-kml-layer.ts | 5 +- .../map-marker-clusterer.spec.ts | 71 +- .../map-marker-clusterer.ts | 10 +- src/google-maps/map-marker/map-marker.spec.ts | 41 +- src/google-maps/map-marker/map-marker.ts | 5 +- .../map-polygon/map-polygon.spec.ts | 35 +- src/google-maps/map-polygon/map-polygon.ts | 3 + .../map-polyline/map-polyline.spec.ts | 35 +- src/google-maps/map-polyline/map-polyline.ts | 3 + .../map-rectangle/map-rectangle.spec.ts | 35 +- .../map-rectangle/map-rectangle.ts | 3 + .../map-traffic-layer.spec.ts | 10 +- .../map-traffic-layer/map-traffic-layer.ts | 3 + .../map-transit-layer.spec.ts | 10 +- src/google-maps/package.json | 2 +- src/google-maps/schematics/BUILD.bazel | 2 +- src/google-maps/schematics/migration.json | 8 +- src/google-maps/schematics/ng-update/index.ts | 4 +- .../testing/fake-google-map-utils.ts | 46 +- .../adapter/date-fns-adapter.ts | 6 +- .../schematics/BUILD.bazel | 2 +- src/material-experimental/README.md | 13 +- .../column-resize/column-resize.spec.ts | 252 +- .../column-resize/overlay-handle.ts | 2 + .../column-resize/resize-strategy.ts | 4 +- .../menubar/menubar-item.spec.ts | 3 +- .../menubar/menubar-item.ts | 3 +- .../menubar/menubar.spec.ts | 3 +- src/material-experimental/menubar/menubar.ts | 3 +- .../popover-edit/popover-edit.spec.ts | 282 +- .../selection/selection-column.ts | 2 + .../adapter/luxon-date-adapter.spec.ts | 16 - .../adapter/luxon-date-adapter.ts | 11 +- .../schematics/BUILD.bazel | 2 +- .../adapter/moment-date-adapter.ts | 6 +- .../adapter/moment-locales.spec.ts | 2 - .../schematics/BUILD.bazel | 2 +- src/material/_index.scss | 3 +- .../autocomplete/autocomplete-origin.ts | 3 + .../autocomplete/autocomplete-trigger.ts | 80 +- src/material/autocomplete/autocomplete.md | 2 +- .../autocomplete/autocomplete.spec.ts | 950 +- src/material/autocomplete/autocomplete.ts | 4 + .../autocomplete/autocomplete.zone.spec.ts | 2 - .../testing/autocomplete-harness.spec.ts | 5 +- src/material/badge/badge.spec.ts | 55 +- src/material/badge/badge.ts | 37 +- src/material/badge/public-api.ts | 2 +- .../badge/testing/badge-harness.spec.ts | 3 +- .../bottom-sheet/bottom-sheet-config.ts | 18 +- .../bottom-sheet/bottom-sheet-container.ts | 38 +- src/material/bottom-sheet/bottom-sheet.md | 10 +- .../bottom-sheet/bottom-sheet.spec.ts | 327 +- src/material/bottom-sheet/bottom-sheet.ts | 7 +- .../testing/bottom-sheet-harness.spec.ts | 3 +- .../button-toggle/button-toggle.spec.ts | 124 +- src/material/button-toggle/button-toggle.ts | 6 + .../testing/button-toggle-group.spec.ts | 3 +- .../testing/button-toggle-harness.spec.ts | 3 +- src/material/button/BUILD.bazel | 1 - src/material/button/_m3-button.scss | 1 - src/material/button/button-base.ts | 5 +- src/material/button/button.html | 21 +- src/material/button/button.md | 49 - src/material/button/button.scss | 28 - src/material/button/button.spec.ts | 95 +- src/material/button/button.ts | 5 +- src/material/button/fab.scss | 34 +- src/material/button/fab.ts | 7 + src/material/button/icon-button.html | 6 - src/material/button/icon-button.scss | 32 - src/material/button/icon-button.ts | 5 +- .../button/testing/button-harness.spec.ts | 55 +- src/material/button/testing/button-harness.ts | 7 - src/material/card/card.spec.ts | 4 +- src/material/card/card.ts | 6 + .../card/testing/card-harness.spec.ts | 3 +- src/material/checkbox/checkbox.html | 16 +- src/material/checkbox/checkbox.spec.ts | 306 +- src/material/checkbox/checkbox.ts | 6 +- .../checkbox/testing/checkbox-harness.spec.ts | 3 +- src/material/chips/chip-action.ts | 4 +- src/material/chips/chip-edit-input.spec.ts | 3 +- src/material/chips/chip-edit-input.ts | 3 + src/material/chips/chip-grid.spec.ts | 182 +- src/material/chips/chip-grid.ts | 22 +- src/material/chips/chip-icons.ts | 4 +- src/material/chips/chip-input.spec.ts | 29 +- src/material/chips/chip-input.ts | 2 + src/material/chips/chip-listbox.spec.ts | 141 +- src/material/chips/chip-listbox.ts | 2 + src/material/chips/chip-option.spec.ts | 15 +- src/material/chips/chip-option.ts | 2 + src/material/chips/chip-remove.spec.ts | 45 +- src/material/chips/chip-row.spec.ts | 61 +- src/material/chips/chip-row.ts | 4 + src/material/chips/chip-set.spec.ts | 12 +- src/material/chips/chip-set.ts | 9 +- src/material/chips/chip.scss | 13 +- src/material/chips/chip.spec.ts | 6 +- src/material/chips/chip.ts | 16 +- src/material/chips/chips.md | 6 +- .../chips/testing/chip-grid-harness.spec.ts | 3 +- .../chips/testing/chip-harness.spec.ts | 3 +- .../chips/testing/chip-input-harness.spec.ts | 3 +- .../testing/chip-listbox-harness.spec.ts | 3 +- .../chips/testing/chip-option-harness.spec.ts | 3 +- .../chips/testing/chip-row-harness.spec.ts | 3 +- .../chips/testing/chip-set-harness.spec.ts | 3 +- src/material/chips/tokens.ts | 3 +- .../core/common-behaviors/error-state.ts | 41 +- .../core/datetime/native-date-adapter.ts | 6 +- src/material/core/error/error-options.ts | 27 +- .../core/focus-indicators/_private.scss | 5 +- .../focus-indicators/structural-styles.scss | 2 +- .../focus-indicators/structural-styles.ts | 3 +- .../internal-form-field.scss | 12 +- .../internal-form-field.ts | 6 +- src/material/core/option/optgroup.ts | 4 + src/material/core/option/option.spec.ts | 4 +- src/material/core/option/option.ts | 3 + src/material/core/private/ripple-loader.ts | 4 +- src/material/core/ripple/ripple-renderer.ts | 10 +- src/material/core/ripple/ripple.md | 2 +- src/material/core/ripple/ripple.spec.ts | 15 +- src/material/core/ripple/ripple.ts | 2 + src/material/core/ripple/ripple.zone.spec.ts | 3 +- .../pseudo-checkbox/pseudo-checkbox.ts | 6 +- .../core/testing/optgroup-harness.spec.ts | 3 +- .../core/testing/option-harness.spec.ts | 3 +- src/material/core/theming/BUILD.bazel | 5 - src/material/core/theming/_typography.scss | 78 - src/material/core/tokens/BUILD.bazel | 1 - src/material/core/tokens/_classes.scss | 47 +- src/material/core/tokens/_system.scss | 22 +- .../datepicker/aria-accessible-name.ts | 4 +- src/material/datepicker/calendar-body.html | 2 +- src/material/datepicker/calendar-body.scss | 4 - src/material/datepicker/calendar-body.spec.ts | 15 +- src/material/datepicker/calendar-body.ts | 17 +- .../datepicker/calendar-header.spec.ts | 4 +- src/material/datepicker/calendar.spec.ts | 6 +- src/material/datepicker/calendar.ts | 19 +- .../datepicker/date-range-input-parts.ts | 8 +- .../datepicker/date-range-input.spec.ts | 106 +- src/material/datepicker/date-range-input.ts | 6 +- src/material/datepicker/date-range-picker.ts | 3 +- .../datepicker/datepicker-actions.spec.ts | 67 +- src/material/datepicker/datepicker-actions.ts | 12 + src/material/datepicker/datepicker-base.ts | 8 +- .../datepicker/datepicker-content.scss | 4 - .../datepicker/datepicker-input-base.ts | 4 +- src/material/datepicker/datepicker-input.ts | 2 + src/material/datepicker/datepicker-intl.ts | 4 +- src/material/datepicker/datepicker-toggle.ts | 6 +- src/material/datepicker/datepicker.spec.ts | 732 +- src/material/datepicker/datepicker.ts | 3 +- src/material/datepicker/month-view.spec.ts | 5 +- src/material/datepicker/month-view.ts | 6 +- .../datepicker/multi-year-view.spec.ts | 6 +- src/material/datepicker/multi-year-view.ts | 4 + src/material/datepicker/testing/BUILD.bazel | 1 - .../testing/calendar-harness.spec.ts | 3 +- .../testing/date-range-input-harness.spec.ts | 3 +- .../datepicker-actions-harness.spec.ts | 78 - .../testing/datepicker-actions-harness.ts | 58 - .../testing/datepicker-harness-filters.ts | 3 - .../testing/datepicker-input-harness.spec.ts | 3 +- .../testing/datepicker-toggle-harness.spec.ts | 3 +- src/material/datepicker/testing/public-api.ts | 1 - src/material/datepicker/year-view.spec.ts | 5 +- src/material/datepicker/year-view.ts | 4 + src/material/dialog/dialog-config.ts | 17 +- src/material/dialog/dialog-container.ts | 2 +- .../dialog/dialog-content-directives.ts | 17 +- src/material/dialog/dialog.md | 28 +- src/material/dialog/dialog.spec.ts | 679 +- src/material/dialog/dialog.ts | 6 +- src/material/dialog/dialog.zone.spec.ts | 17 +- .../dialog/testing/dialog-harness.spec.ts | 3 +- .../dialog/testing/dialog-opener.spec.ts | 21 +- src/material/dialog/testing/dialog-opener.ts | 13 +- src/material/divider/divider.spec.ts | 3 +- src/material/divider/divider.ts | 3 +- .../divider/testing/divider-harness.spec.ts | 3 +- src/material/expansion/accordion.spec.ts | 13 +- .../expansion/expansion-panel-content.ts | 3 + .../expansion/expansion-panel-header.scss | 6 +- .../expansion/expansion-panel-header.ts | 4 + src/material/expansion/expansion-panel.ts | 8 +- src/material/expansion/expansion.spec.ts | 10 +- .../testing/expansion-harness.spec.ts | 3 +- src/material/form-field/directives/error.ts | 23 +- .../form-field/directives/floating-label.ts | 3 + .../form-field/directives/line-ripple.ts | 2 + .../form-field/directives/notched-outline.ts | 2 + src/material/form-field/form-field.html | 2 +- src/material/form-field/form-field.md | 14 +- src/material/form-field/form-field.ts | 6 +- .../testing/form-field-harness.spec.ts | 3 +- src/material/grid-list/grid-list.spec.ts | 34 +- src/material/grid-list/grid-list.ts | 5 + src/material/grid-list/grid-tile.ts | 9 + .../testing/grid-list-harness.spec.ts | 3 +- src/material/grid-list/tile-coordinator.ts | 5 +- src/material/icon/icon.md | 4 +- src/material/icon/icon.spec.ts | 121 +- src/material/icon/icon.ts | 4 + .../icon/testing/fake-icon-registry.ts | 4 +- .../icon/testing/icon-harness.spec.ts | 3 +- src/material/input/input.md | 4 +- src/material/input/input.spec.ts | 406 +- src/material/input/input.ts | 6 +- .../input/testing/input-harness.spec.ts | 3 +- .../testing/native-select-harness.spec.ts | 3 +- src/material/list/action-list.ts | 3 +- src/material/list/list-base.ts | 2 + src/material/list/list-item-sections.ts | 9 + src/material/list/list-option.ts | 15 + src/material/list/list.spec.ts | 38 +- src/material/list/list.ts | 3 + src/material/list/nav-list.ts | 3 +- src/material/list/public-api.ts | 10 +- src/material/list/selection-list.spec.ts | 212 +- src/material/list/selection-list.ts | 40 +- .../list/testing/list-harness.spec.ts | 6 +- .../menu/context-menu-trigger.spec.ts | 23 +- src/material/menu/menu-content.ts | 4 + src/material/menu/menu-item.ts | 4 + src/material/menu/menu-trigger-base.ts | 4 +- src/material/menu/menu-trigger.ts | 2 + src/material/menu/menu.md | 4 - src/material/menu/menu.spec.ts | 737 +- src/material/menu/menu.ts | 9 +- .../menu/testing/context-menu-harness.spec.ts | 3 +- .../menu/testing/menu-harness.spec.ts | 4 +- src/material/paginator/paginator-intl.ts | 4 +- src/material/paginator/paginator.spec.ts | 16 +- src/material/paginator/paginator.ts | 4 + .../testing/paginator-harness.spec.ts | 3 +- .../progress-bar/progress-bar.spec.ts | 3 - src/material/progress-bar/progress-bar.ts | 4 + .../testing/progress-bar-harness.spec.ts | 3 +- .../progress-spinner/progress-spinner.spec.ts | 11 +- .../progress-spinner/progress-spinner.ts | 4 + .../testing/progress-spinner-harness.spec.ts | 3 +- src/material/radio/_radio-common.scss | 3 +- src/material/radio/radio.html | 31 +- src/material/radio/radio.scss | 15 +- src/material/radio/radio.spec.ts | 16 +- src/material/radio/radio.ts | 19 + .../radio/testing/radio-harness.spec.ts | 3 +- src/material/schematics/BUILD.bazel | 3 +- src/material/schematics/migration.json | 8 +- .../ng-generate/table/index.spec.ts | 16 +- .../ng-generate/theme-color/BUILD.bazel | 2 +- .../ng-generate/theme-color/index.ts | 5 +- .../schematics/ng-generate/tree/index.spec.ts | 16 +- src/material/schematics/ng-update/index.ts | 6 +- src/material/select/select.md | 4 +- src/material/select/select.scss | 22 +- src/material/select/select.spec.ts | 1009 +- src/material/select/select.ts | 85 +- .../select/testing/select-harness.spec.ts | 3 +- src/material/sidenav/drawer-container.html | 8 +- src/material/sidenav/drawer.spec.ts | 624 +- src/material/sidenav/drawer.ts | 119 +- src/material/sidenav/sidenav-container.html | 8 +- src/material/sidenav/sidenav.spec.ts | 21 +- src/material/sidenav/sidenav.ts | 8 +- .../sidenav/testing/sidenav-harness.spec.ts | 4 +- src/material/slide-toggle/slide-toggle.md | 7 - src/material/slide-toggle/slide-toggle.scss | 25 - .../slide-toggle/slide-toggle.spec.ts | 212 +- src/material/slide-toggle/slide-toggle.ts | 10 +- .../testing/slide-toggle-harness.spec.ts | 3 +- src/material/slider/slider-input.ts | 14 +- src/material/slider/slider-interface.ts | 6 +- src/material/slider/slider-thumb.ts | 5 + src/material/slider/slider.e2e.spec.ts | 2 +- src/material/slider/slider.spec.ts | 542 +- src/material/slider/slider.ts | 43 +- .../slider/testing/slider-harness.spec.ts | 9 +- src/material/snack-bar/simple-snack-bar.ts | 6 +- src/material/snack-bar/snack-bar-container.ts | 4 +- src/material/snack-bar/snack-bar.md | 10 +- src/material/snack-bar/snack-bar.spec.ts | 322 +- src/material/snack-bar/snack-bar.ts | 7 +- src/material/snack-bar/snack-bar.zone.spec.ts | 27 +- .../testing/snack-bar-harness.spec.ts | 3 +- src/material/sort/sort-header-intl.ts | 8 +- src/material/sort/sort-header.scss | 4 +- src/material/sort/sort-header.ts | 28 + src/material/sort/sort.spec.ts | 12 +- .../sort/testing/sort-harness.spec.ts | 3 +- src/material/stepper/_m2-stepper.scss | 4 +- src/material/stepper/step-content.ts | 3 + src/material/stepper/step-header.scss | 8 - src/material/stepper/step-header.ts | 4 + src/material/stepper/stepper-icon.ts | 3 + src/material/stepper/stepper-intl.ts | 4 +- src/material/stepper/stepper.html | 12 +- src/material/stepper/stepper.md | 26 +- src/material/stepper/stepper.spec.ts | 53 +- src/material/stepper/stepper.ts | 26 +- .../stepper/testing/stepper-harness.spec.ts | 3 +- src/material/table/row.ts | 6 +- src/material/table/table-data-source.spec.ts | 13 +- src/material/table/table.spec.ts | 12 +- src/material/table/table.ts | 2 +- .../table/testing/table-harness.spec.ts | 3 +- src/material/table/text-column.ts | 2 +- src/material/tabs/_tabs-common.scss | 5 +- src/material/tabs/paginated-tab-header.ts | 15 +- src/material/tabs/tab-body.scss | 2 +- src/material/tabs/tab-body.spec.ts | 2 - src/material/tabs/tab-body.ts | 10 +- src/material/tabs/tab-content.ts | 3 + src/material/tabs/tab-group.html | 4 +- src/material/tabs/tab-group.spec.ts | 317 +- src/material/tabs/tab-group.ts | 38 +- src/material/tabs/tab-header.spec.ts | 177 +- src/material/tabs/tab-header.ts | 2 +- .../tabs/tab-nav-bar/tab-nav-bar.spec.ts | 87 +- src/material/tabs/tab-nav-bar/tab-nav-bar.ts | 29 +- src/material/tabs/tab.ts | 5 +- .../tabs/testing/tab-group-harness.spec.ts | 3 +- .../tabs/testing/tab-nav-bar-harness.spec.ts | 3 +- .../testing/timepicker-harness.spec.ts | 3 +- .../testing/timepicker-input-harness.spec.ts | 3 +- .../testing/timepicker-toggle-harness.spec.ts | 3 +- src/material/timepicker/timepicker-toggle.ts | 2 + src/material/timepicker/timepicker.spec.ts | 114 +- src/material/timepicker/timepicker.ts | 2 + src/material/timepicker/util.ts | 2 - .../toolbar/testing/toolbar-harness.spec.ts | 3 +- src/material/toolbar/toolbar.md | 7 - src/material/toolbar/toolbar.spec.ts | 6 +- src/material/toolbar/toolbar.ts | 5 + src/material/tooltip/_m3-tooltip.scss | 5 +- .../tooltip/testing/tooltip-harness.spec.ts | 3 +- src/material/tooltip/tooltip.spec.ts | 521 +- src/material/tooltip/tooltip.ts | 65 +- src/material/tooltip/tooltip.zone.spec.ts | 20 +- src/material/tree/node.ts | 2 + .../tree/testing/tree-harness.spec.ts | 7 +- .../tree-using-legacy-key-manager.spec.ts | 10 +- .../tree/tree-using-tree-control.spec.ts | 58 +- src/material/tree/tree.spec.ts | 56 +- src/material/tree/tree.ts | 2 +- src/tsconfig-legacy.json | 1 - src/universal-app/hydration.e2e.spec.ts | 27 +- .../kitchen-sink/kitchen-sink.html | 8 + .../kitchen-sink/kitchen-sink.ts | 2 + src/youtube-player/BUILD.bazel | 1 - src/youtube-player/README.md | 17 +- src/youtube-player/package.json | 2 +- src/youtube-player/schematics/BUILD.bazel | 2 +- .../youtube-player-placeholder.ts | 55 +- src/youtube-player/youtube-player.spec.ts | 66 +- src/youtube-player/youtube-player.ts | 45 +- tools/bazel/web_test_suite.bzl | 12 +- tools/dgeni/common/decorators.ts | 2 +- tools/extract-tokens/extract-tokens.mts | 3 +- .../markdown-to-html/docs-marked-renderer.mts | 21 +- tools/tsconfig.json | 1 + tsconfig.json | 1 - tslint.json | 3 +- 1417 files changed, 36955 insertions(+), 42060 deletions(-) create mode 100644 .npmrc create mode 100644 docs/.npmrc delete mode 100644 docs/src/assets/img/android-chrome-192x192.png delete mode 100644 docs/src/assets/img/android-chrome-512x512.png delete mode 100644 docs/src/assets/img/apple-touch-icon.png delete mode 100644 docs/src/assets/img/favicon-16x16.png delete mode 100644 docs/src/assets/img/favicon-32x32.png delete mode 100644 docs/src/assets/img/favicon-48x48.png create mode 100644 docs/src/assets/img/manifest.json delete mode 100644 docs/src/assets/img/safari-pinned-tab.svg delete mode 100644 docs/src/assets/img/site.webmanifest delete mode 100644 goldens/aria/accordion/testing/index.api.md delete mode 100644 goldens/aria/combobox/testing/index.api.md delete mode 100644 goldens/aria/grid/testing/index.api.md delete mode 100644 goldens/aria/listbox/testing/index.api.md delete mode 100644 goldens/aria/menu/testing/index.api.md delete mode 100644 goldens/aria/tabs/testing/index.api.md delete mode 100644 goldens/aria/toolbar/testing/index.api.md delete mode 100644 goldens/aria/tree/testing/index.api.md delete mode 100644 src/aria/accordion/testing/BUILD.bazel delete mode 100644 src/aria/accordion/testing/accordion-harness-filters.ts delete mode 100644 src/aria/accordion/testing/accordion-harness.spec.ts delete mode 100644 src/aria/accordion/testing/accordion-harness.ts delete mode 100644 src/aria/accordion/testing/index.ts delete mode 100644 src/aria/accordion/testing/public-api.ts create mode 100644 src/aria/combobox/combobox-dialog.ts create mode 100644 src/aria/combobox/combobox-input.ts create mode 100644 src/aria/combobox/combobox-popup-container.ts delete mode 100644 src/aria/combobox/combobox-widget.ts delete mode 100644 src/aria/combobox/combobox.zone.spec.ts delete mode 100644 src/aria/combobox/testing/BUILD.bazel delete mode 100644 src/aria/combobox/testing/combobox-harness-filters.ts delete mode 100644 src/aria/combobox/testing/combobox-harness.spec.ts delete mode 100644 src/aria/combobox/testing/combobox-harness.ts delete mode 100644 src/aria/combobox/testing/index.ts delete mode 100644 src/aria/combobox/testing/public-api.ts delete mode 100644 src/aria/grid/grid.spec.ts delete mode 100644 src/aria/grid/testing/BUILD.bazel delete mode 100644 src/aria/grid/testing/grid-harness-filters.ts delete mode 100644 src/aria/grid/testing/grid-harness.spec.ts delete mode 100644 src/aria/grid/testing/grid-harness.ts delete mode 100644 src/aria/grid/testing/index.ts delete mode 100644 src/aria/grid/testing/public-api.ts delete mode 100644 src/aria/listbox/testing/BUILD.bazel delete mode 100644 src/aria/listbox/testing/index.ts delete mode 100644 src/aria/listbox/testing/listbox-harness-filters.ts delete mode 100644 src/aria/listbox/testing/listbox-harness.spec.ts delete mode 100644 src/aria/listbox/testing/listbox-harness.ts delete mode 100644 src/aria/listbox/testing/public-api.ts delete mode 100644 src/aria/menu/testing/BUILD.bazel delete mode 100644 src/aria/menu/testing/index.ts delete mode 100644 src/aria/menu/testing/menu-harness-filters.ts delete mode 100644 src/aria/menu/testing/menu-harness.spec.ts delete mode 100644 src/aria/menu/testing/menu-harness.ts delete mode 100644 src/aria/menu/testing/public-api.ts delete mode 100644 src/aria/private/behaviors/event-manager/click-event-manager.ts rename src/aria/private/{utils => behaviors/grid-focus}/BUILD.bazel (53%) create mode 100644 src/aria/private/behaviors/grid-focus/grid-focus.spec.ts create mode 100644 src/aria/private/behaviors/grid-focus/grid-focus.ts create mode 100644 src/aria/private/behaviors/grid-navigation/BUILD.bazel create mode 100644 src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts create mode 100644 src/aria/private/behaviors/grid-navigation/grid-navigation.ts create mode 100644 src/aria/private/listbox/combobox-listbox.ts delete mode 100644 src/aria/private/testing/BUILD.bazel delete mode 100644 src/aria/private/testing/test-helpers.ts create mode 100644 src/aria/private/tree/combobox-tree.ts delete mode 100644 src/aria/private/utils/collection.spec.ts delete mode 100644 src/aria/private/utils/collection.ts delete mode 100644 src/aria/private/utils/element-resolver.spec.ts delete mode 100644 src/aria/private/utils/element-resolver.ts delete mode 100644 src/aria/private/utils/transforms.ts delete mode 100644 src/aria/private/utils/violations.ts delete mode 100644 src/aria/tabs/testing/BUILD.bazel delete mode 100644 src/aria/tabs/testing/index.ts delete mode 100644 src/aria/tabs/testing/public-api.ts delete mode 100644 src/aria/tabs/testing/tabs-harness-filters.ts delete mode 100644 src/aria/tabs/testing/tabs-harness.spec.ts delete mode 100644 src/aria/tabs/testing/tabs-harness.ts rename src/aria/tabs/{tab-tokens.ts => utils.ts} (58%) delete mode 100644 src/aria/toolbar/testing/BUILD.bazel delete mode 100644 src/aria/toolbar/testing/index.ts delete mode 100644 src/aria/toolbar/testing/public-api.ts delete mode 100644 src/aria/toolbar/testing/toolbar-harness-filters.ts delete mode 100644 src/aria/toolbar/testing/toolbar-harness.spec.ts delete mode 100644 src/aria/toolbar/testing/toolbar-harness.ts delete mode 100644 src/aria/toolbar/testing/toolbar-widget-group-harness.ts delete mode 100644 src/aria/toolbar/testing/toolbar-widget-harness.ts rename src/aria/toolbar/{toolbar-tokens.ts => utils.ts} (68%) delete mode 100644 src/aria/tree/testing/BUILD.bazel delete mode 100644 src/aria/tree/testing/index.ts delete mode 100644 src/aria/tree/testing/item-harness.ts delete mode 100644 src/aria/tree/testing/public-api.ts delete mode 100644 src/aria/tree/testing/tree-harness-filters.ts delete mode 100644 src/aria/tree/testing/tree-harness.spec.ts delete mode 100644 src/aria/tree/testing/tree-harness.ts rename src/aria/{private/utils/element.ts => tree/utils.ts} (100%) create mode 100644 src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.css delete mode 100644 src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.ts create mode 100644 src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css delete mode 100644 src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-example.css create mode 100644 src/components-examples/aria/combobox/combobox-examples.css delete mode 100644 src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.ts create mode 100644 src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.html create mode 100644 src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-select/combobox-select-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-select/combobox-select-example.ts create mode 100644 src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html create mode 100644 src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts delete mode 100644 src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.html delete mode 100644 src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.ts delete mode 100644 src/components-examples/aria/combobox/countries.ts create mode 100644 src/components-examples/aria/combobox/data.ts rename src/components-examples/aria/combobox/{combobox-select/combobox-select-example.css => select-examples.css} (69%) delete mode 100644 src/components-examples/aria/combobox/states.ts delete mode 100644 src/components-examples/aria/menu/menu-cdk-overlay/menu-cdk-overlay-example.html delete mode 100644 src/components-examples/aria/menu/menu-cdk-overlay/menu-cdk-overlay-example.ts create mode 100644 src/components-examples/aria/select/BUILD.bazel create mode 100644 src/components-examples/aria/select/index.ts create mode 100644 src/components-examples/aria/select/select-disabled/select-disabled-example.html create mode 100644 src/components-examples/aria/select/select-disabled/select-disabled-example.ts create mode 100644 src/components-examples/aria/select/select-multi/select-multi-example.html create mode 100644 src/components-examples/aria/select/select-multi/select-multi-example.ts create mode 100644 src/components-examples/aria/select/select.css create mode 100644 src/components-examples/aria/select/select/select-example.html create mode 100644 src/components-examples/aria/select/select/select-example.ts delete mode 100644 src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.css delete mode 100644 src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html delete mode 100644 src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.ts delete mode 100644 src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.html delete mode 100644 src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.ts delete mode 100644 src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.css delete mode 100644 src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.html delete mode 100644 src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.ts delete mode 100644 src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.css delete mode 100644 src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.html delete mode 100644 src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.ts delete mode 100644 src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.css delete mode 100644 src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.html delete mode 100644 src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.ts create mode 100644 src/dev-app/aria-select/BUILD.bazel create mode 100644 src/dev-app/aria-select/select-demo.css create mode 100644 src/dev-app/aria-select/select-demo.html create mode 100644 src/dev-app/aria-select/select-demo.ts create mode 100644 src/google-maps/map-heatmap-layer/README.md create mode 100644 src/google-maps/map-heatmap-layer/map-heatmap-layer.spec.ts delete mode 100644 src/material-moment-adapter/adapter/moment-locales.spec.ts delete mode 100644 src/material/core/theming/_typography.scss delete mode 100644 src/material/datepicker/testing/datepicker-actions-harness.spec.ts delete mode 100644 src/material/datepicker/testing/datepicker-actions-harness.ts diff --git a/.bazelversion b/.bazelversion index df5119ec64e6..f9c71a52e2fd 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -8.7.0 +8.5.1 diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index 9da02b0b8a25..abc91842d69f 100644 --- a/.github/workflows/assistant-to-the-branch-manager.yml +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -13,9 +13,9 @@ jobs: assistant_to_the_branch_manager: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: angular/dev-infra/github-actions/branch-manager@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: angular/dev-infra/github-actions/branch-manager@0edcf22f9ce677699274f2d399325f99d3d56e81 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.material-aio.yml b/.github/workflows/ci.material-aio.yml index a1b57dff55ff..18bbbe24dcbe 100644 --- a/.github/workflows/ci.material-aio.yml +++ b/.github/workflows/ci.material-aio.yml @@ -21,11 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Build @@ -35,17 +35,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Tests run: pnpm bazel test --test_tag_filters=-e2e,-audit //docs/... - name: Store Test Logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: always() with: name: test-logs @@ -56,17 +56,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Lighthouse Audit run: pnpm -s bazel test --test_tag_filters=audit //docs/... - name: Store Audit Logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: always() with: name: lighthouse-logs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec3a69832b51..94df3bf263b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile # TODO: Remove --ignore_all_rc_files flag once a repository can be loaded in bazelrc during info @@ -49,11 +49,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -65,11 +65,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -81,11 +81,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -98,11 +98,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -114,11 +114,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -130,11 +130,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build Snapshots @@ -154,15 +154,15 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 # See: https://github.com/puppeteer/puppeteer/pull/13196 and # https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md. - name: Disable AppArmor run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build and Verify Release Output @@ -185,12 +185,12 @@ jobs: CI_RUNNER_NUMBER: ${{ github.run_id }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Browserstack Variables - uses: angular/dev-infra/github-actions/browserstack@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/browserstack@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Run tests on Browserstack run: ./scripts/circleci/run-browserstack-tests.sh diff --git a/.github/workflows/deploy-dev-app-main-push.yml b/.github/workflows/deploy-dev-app-main-push.yml index 7f8afda48239..8ed0a487511c 100644 --- a/.github/workflows/deploy-dev-app-main-push.yml +++ b/.github/workflows/deploy-dev-app-main-push.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile @@ -36,7 +36,7 @@ jobs: npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting dev-app npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting dev-app ${{env.PREVIEW_SITE}} - - uses: FirebaseExtended/action-hosting-deploy@500ac625ca2dd40cbd15f7659af953801858032a # v0 + - uses: FirebaseExtended/action-hosting-deploy@e2eda2e106cfa35cdbcf4ac9ddaf6c4756df2c8c # v0 id: deploy with: # Note: No token used here as the action otherwise may attempt to post a comment. diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml index e0efdf2b94e4..453a0139f3ff 100644 --- a/.github/workflows/dev-infra.yml +++ b/.github/workflows/dev-infra.yml @@ -3,33 +3,22 @@ name: DevInfra on: pull_request_target: types: [opened, synchronize, reopened] - issues: - types: [opened, reopened] # Declare default permissions as read only. -permissions: - contents: read +permissions: read-all jobs: labels: - if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/labeling/pull-request@649c3afeaa46674507b9625537e49de54a695e2b + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: angular/dev-infra/github-actions/pull-request-labeling@0edcf22f9ce677699274f2d399325f99d3d56e81 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} post_approval_changes: - if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/post-approval-changes@649c3afeaa46674507b9625537e49de54a695e2b + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: angular/dev-infra/github-actions/pull-request-labeling@0edcf22f9ce677699274f2d399325f99d3d56e81 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} - issue_labels: - if: github.event_name == 'issues' - runs-on: ubuntu-latest - steps: - - uses: angular/dev-infra/github-actions/labeling/issue@649c3afeaa46674507b9625537e49de54a695e2b - with: - angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} - google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }} diff --git a/.github/workflows/docs-preview-build.yml b/.github/workflows/docs-preview-build.yml index 87b95f3d7142..4cb414295b27 100644 --- a/.github/workflows/docs-preview-build.yml +++ b/.github/workflows/docs-preview-build.yml @@ -21,16 +21,16 @@ jobs: (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'docs: preview')) steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build docs site run: pnpm bazel build //docs:build.production - - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@0edcf22f9ce677699274f2d399325f99d3d56e81 with: workflow-artifact-name: 'docs-preview' pull-number: '${{github.event.pull_request.number}}' diff --git a/.github/workflows/docs-preview-deploy.yml b/.github/workflows/docs-preview-deploy.yml index 8d4d3d039eb6..8d94f40d47cf 100644 --- a/.github/workflows/docs-preview-deploy.yml +++ b/.github/workflows/docs-preview-deploy.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: '${{secrets.GITHUB_TOKEN}}' @@ -40,7 +40,7 @@ jobs: npx -y firebase-tools@latest target:clear --config docs/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting mat-aio npx -y firebase-tools@latest target:apply --config docs/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting mat-aio ${{env.PREVIEW_SITE}} - - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@0edcf22f9ce677699274f2d399325f99d3d56e81 with: github-token: '${{secrets.GITHUB_TOKEN}}' workflow-artifact-name: 'docs-preview' diff --git a/.github/workflows/google-internal-tests.yml b/.github/workflows/google-internal-tests.yml index 963b8981abc3..8a8bf4e484be 100644 --- a/.github/workflows/google-internal-tests.yml +++ b/.github/workflows/google-internal-tests.yml @@ -12,8 +12,8 @@ jobs: trigger: runs-on: ubuntu-latest steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: angular/dev-infra/github-actions/google-internal-tests@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: angular/dev-infra/github-actions/google-internal-tests@0edcf22f9ce677699274f2d399325f99d3d56e81 with: run-tests-guide-url: http://go/angular-material-presubmit github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr.material-aio.yml b/.github/workflows/pr.material-aio.yml index 477e3c848184..7a9cf45a9deb 100644 --- a/.github/workflows/pr.material-aio.yml +++ b/.github/workflows/pr.material-aio.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Build @@ -33,17 +33,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Tests run: pnpm bazel test --test_tag_filters=-e2e,-audit //docs/... - name: Store Test Logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: always() with: name: test-logs @@ -54,17 +54,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Lighthouse Audit run: pnpm bazel test --test_tag_filters=audit //docs/... - name: Store Audit Logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: always() with: name: lighthouse-logs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2e411eb52358..bdf5b820097d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile # TODO: Remove --ignore_all_rc_files flag once a repository can be loaded in bazelrc during info @@ -45,7 +45,7 @@ jobs: - name: Check code format run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }} - name: Check Package Licenses - uses: angular/dev-infra/github-actions/linting/licenses@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/linting/licenses@0edcf22f9ce677699274f2d399325f99d3d56e81 # Commit message check is last intentionally, because the caretaker can fix it # during merge, while other lint failures have to be resolved by the PR author. - name: Check commit message @@ -55,11 +55,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Check API Goldens @@ -69,11 +69,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run e2e tests @@ -83,11 +83,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run integration tests @@ -97,11 +97,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -111,11 +111,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -125,11 +125,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build and Verify Release Output @@ -139,7 +139,7 @@ jobs: - name: Build and create package artifacts run: ./scripts/create-package-archives.mjs --suffix "pr${{github.event.number}}-$(git rev-parse --short HEAD)" - name: Upload artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: Package Artifacts path: dist/release-archives @@ -152,15 +152,15 @@ jobs: CI_RUNNER_NUMBER: ${{ github.run_id }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 with: # Checking out the pull request commit is intended here as we need to run the changed code tests. ref: ${{ github.event.pull_request.head.sha }} - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Browserstack Variables - uses: angular/dev-infra/github-actions/browserstack@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/browserstack@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Run tests on Browserstack run: ./scripts/circleci/run-browserstack-tests.sh diff --git a/.github/workflows/preview-build-dev-app.yml b/.github/workflows/preview-build-dev-app.yml index b3423d4d55d6..d1a0a3cd6480 100644 --- a/.github/workflows/preview-build-dev-app.yml +++ b/.github/workflows/preview-build-dev-app.yml @@ -23,16 +23,16 @@ jobs: (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'dev-app preview')) steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 # Build the web package - run: bazel build //src/dev-app:web_package --symlink_prefix=dist/ - - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@0edcf22f9ce677699274f2d399325f99d3d56e81 with: workflow-artifact-name: 'dev-app' pull-number: '${{github.event.pull_request.number}}' diff --git a/.github/workflows/preview-deploy-dev-app.yml b/.github/workflows/preview-deploy-dev-app.yml index d77874b75cc4..b28a7f6b113f 100644 --- a/.github/workflows/preview-deploy-dev-app.yml +++ b/.github/workflows/preview-deploy-dev-app.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Configure Firebase deploy target run: | @@ -33,7 +33,7 @@ jobs: npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting dev-app npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting dev-app ${{env.PREVIEW_SITE}} - - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@ba726e7bca0b08b125ccc6f93c233749e1213c17 + - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@0edcf22f9ce677699274f2d399325f99d3d56e81 with: github-token: '${{secrets.GITHUB_TOKEN}}' workflow-artifact-name: 'dev-app' diff --git a/.github/workflows/scheduled-ci.yml b/.github/workflows/scheduled-ci.yml index 50a3b79c3b64..a37155ba9e02 100644 --- a/.github/workflows/scheduled-ci.yml +++ b/.github/workflows/scheduled-ci.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Setting up Angular snapshot builds @@ -31,7 +31,7 @@ jobs: # updates the lock file as expected with the changes run: node ./scripts/circleci/setup-angular-snapshots.js main - name: Install node modules - run: pnpm install --no-frozen-lockfile + run: pnpm install - name: Run Browser tests run: pnpm bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... @@ -39,11 +39,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/setup@0edcf22f9ce677699274f2d399325f99d3d56e81 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/bazel/configure-remote@0edcf22f9ce677699274f2d399325f99d3d56e81 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Setting up Angular snapshot builds @@ -51,7 +51,7 @@ jobs: # updates the lock files as expected with the changes run: node ./scripts/circleci/setup-angular-snapshots.js main - name: Install node modules - run: pnpm install --no-frozen-lockfile + run: pnpm install - name: Run linker tests using AOT run: pnpm test-linker-aot - name: Run linker tests using JIT @@ -61,7 +61,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@ba726e7bca0b08b125ccc6f93c233749e1213c17 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0edcf22f9ce677699274f2d399325f99d3d56e81 # See: https://github.com/puppeteer/puppeteer/pull/13196 and # https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md. - name: Disable AppArmor diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 16cb3a2e3a55..e00f8790ea99 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -39,7 +39,7 @@ jobs: # Upload the results as artifacts. - name: 'Upload artifact' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 with: sarif_file: results.sarif diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000000..7c7738880a15 --- /dev/null +++ b/.npmrc @@ -0,0 +1,12 @@ +# Yarn Berry doesn't check engines at all, so pnpm shouldn't either. +engine-strict = false + +# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on +# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what +# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules) +hoist=false + +# Avoid pnpm auto-installing peer dependencies. We want to be explicit about our versions used +# for peer dependencies, avoiding potential mismatches. In addition, it ensures we can continue +# to rely on peer dependency placeholders substituted via Bazel. +auto-install-peers=false diff --git a/.nvmrc b/.nvmrc index f9e7451e7715..85e502778f62 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.23.1 +22.22.0 diff --git a/.pullapprove.yml b/.pullapprove.yml index ea54debe5a95..4412922eade7 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -96,8 +96,11 @@ groups: author_value: 0 # The author of the PR cannot provide an approval for themself reviewers: users: + - adolgachev - crisbeto + - mmalerba - andrewseguin + - wagnermaciel - tjshiu - ok7sai - ~devversion diff --git a/BUILD.bazel b/BUILD.bazel index 30c5d0143445..63731bd19408 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -41,7 +41,7 @@ genrule( ) validate_ts_version_matching( - module_bazel = "MODULE.bazel", + module_lock_file = "MODULE.bazel.lock", package_json = "package.json", ) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e6bfb4a3947..85591a312959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,589 +1,183 @@ - -# 22.0.6 "plaid-panda" (2026-07-22) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [d9fcaced49](https://github.com/angular/components/commit/d9fcaced4950ce6f3d7f759bbeab18134217dbce) | fix | **drag-drop:** reset popover overflow ([#33554](https://github.com/angular/components/pull/33554)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [842bc940b8](https://github.com/angular/components/commit/842bc940b8dd3f1e69c460e724921a5f4ababede) | fix | **expansion:** always reset outline ([#33542](https://github.com/angular/components/pull/33542)) | -| [b5224c68f1](https://github.com/angular/components/commit/b5224c68f1ef1b996462a305d98387c564c36a83) | fix | **list:** make disabled selection list keyboard navigable ([#33544](https://github.com/angular/components/pull/33544)) | -| [354f1ac17d](https://github.com/angular/components/commit/354f1ac17dc8365dacc61a02f3288dc73f676bb8) | fix | **radio:** avoid making the touch target a focus stop ([#33558](https://github.com/angular/components/pull/33558)) | -### material-luxon-adapter -| Commit | Type | Description | -| -- | -- | -- | -| [6e5acc1ecb](https://github.com/angular/components/commit/6e5acc1ecbed97c307a333f79064927534a8794d) | fix | preserve timezone on .clone() ([#33564](https://github.com/angular/components/pull/33564)) | + +# 21.2.0-rc.0 "chlorine-canine" (2026-02-18) - -# 22.1.0-next.4 "plastic box" (2026-07-15) + +# 21.2.0-next.5 "chlorine-cat" (2026-02-18) ### aria | Commit | Type | Description | | -- | -- | -- | -| [def7bc2a69](https://github.com/angular/components/commit/def7bc2a69fb5722da681468e2638a2dceadb3ab) | fix | **combobox:** closing immediately when opening programmatically with zone.js ([#33518](https://github.com/angular/components/pull/33518)) | +| [c57e6a1545](https://github.com/angular/components/commit/c57e6a1545d79d6ad515082fc89f0dfb5cb1ab8f) | fix | **combobox:** fix autocomplete examples and add comments explaining combobox selection behavior ([#32714](https://github.com/angular/components/pull/32714)) | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [67a5031db7](https://github.com/angular/components/commit/67a5031db78e1e6e6e683b864a49e47c38ab81bd) | fix | **overlay:** guard against null document.body before popover support check ([#33403](https://github.com/angular/components/pull/33403)) | -| [c069cab23c](https://github.com/angular/components/commit/c069cab23ca5d1e4faf277e755c543efcca2b45a) | fix | **private:** guard createPolicy against DOM clobbering ([#33410](https://github.com/angular/components/pull/33410)) | +| [b15b48df98](https://github.com/angular/components/commit/b15b48df980b1f86885d4d25fdc8ba58b2bac62b) | fix | **tree:** no nodes focusable if data is replaced ([#32781](https://github.com/angular/components/pull/32781)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [d324696863](https://github.com/angular/components/commit/d324696863cfc4ee7f4475499d7f0dbed8496d74) | fix | **button:** match focus indicator shape to FAB ([#33527](https://github.com/angular/components/pull/33527)) | -| [9a247a8da2](https://github.com/angular/components/commit/9a247a8da23f2c9d5ed03c68d21659f814ae29c6) | fix | **datepicker:** use rounded shape for calendar focus indicators ([#33524](https://github.com/angular/components/pull/33524)) | -| [14d896a936](https://github.com/angular/components/commit/14d896a9366bca6df253af2aade8de4c09f98ba7) | fix | **sort:** double focus indicators when strong focus is enabled ([#33520](https://github.com/angular/components/pull/33520)) | -| [c729db7972](https://github.com/angular/components/commit/c729db79725e819d0ecd3e08cdd0cd876ddae755) | fix | **stepper:** add border radius to header hover state in m2 ([#33529](https://github.com/angular/components/pull/33529)) | -### multiple -| Commit | Type | Description | -| -- | -- | -- | -| [ebd64a1bf8](https://github.com/angular/components/commit/ebd64a1bf8409feede16bef532acf8ffe903d6f0) | fix | pass form field to error state tracker ([#33509](https://github.com/angular/components/pull/33509)) | - - - - -# 22.0.5 "plastic bottle" (2026-07-15) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [e74c69be33](https://github.com/angular/components/commit/e74c69be33ab44f883ac083d369903784e5cb253) | fix | **combobox:** closing immediately when opening programmatically with zone.js ([#33518](https://github.com/angular/components/pull/33518)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [1059f80cef](https://github.com/angular/components/commit/1059f80cef9ad3348c7c859e6dba36a44bfe8abc) | fix | **overlay:** guard against null document.body before popover support check ([#33403](https://github.com/angular/components/pull/33403)) | -| [adbf93559e](https://github.com/angular/components/commit/adbf93559e2c5ed517bf5867cd7b6cec267bfc92) | fix | **private:** guard createPolicy against DOM clobbering ([#33410](https://github.com/angular/components/pull/33410)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [d26e892021](https://github.com/angular/components/commit/d26e8920219e4ea0b02814d362cb9f3b7ecf647f) | fix | **button:** match focus indicator shape to FAB ([#33527](https://github.com/angular/components/pull/33527)) | -| [ea89b9c37f](https://github.com/angular/components/commit/ea89b9c37f6214efc7b2d59decc308246f1ce21a) | fix | **datepicker:** use rounded shape for calendar focus indicators ([#33524](https://github.com/angular/components/pull/33524)) | -| [092b55aa67](https://github.com/angular/components/commit/092b55aa673edc872ea1bdbd3e120001597e975f) | fix | **sort:** double focus indicators when strong focus is enabled ([#33520](https://github.com/angular/components/pull/33520)) | -| [e93bc9e5d6](https://github.com/angular/components/commit/e93bc9e5d6f1b511a9e845fc26c8c2df537260ab) | fix | **stepper:** add border radius to header hover state in m2 ([#33529](https://github.com/angular/components/pull/33529)) | -### multiple -| Commit | Type | Description | -| -- | -- | -- | -| [b2620abaa2](https://github.com/angular/components/commit/b2620abaa20fe494c77438d0df41a56effd577e9) | fix | pass form field to error state tracker ([#33509](https://github.com/angular/components/pull/33509)) | - - - - -# 22.1.0-next.3 "flerovium-fountain" (2026-07-08) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [ee20441fa](https://github.com/angular/components/commit/ee20441fa76f419aadabb48f12cf2efe531f8ef3) | fix | **listbox:** allow navigation to specific index ([#33485](https://github.com/angular/components/pull/33485)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [c5aa5486a](https://github.com/angular/components/commit/c5aa5486ac78c95d83f4de61c0efbc43071b6d4c) | fix | **core:** rework error state matcher to handle signal forms ([#33500](https://github.com/angular/components/pull/33500)) | - - - - -# 22.0.4 "flerovium-foundation" (2026-07-08) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [918090a07](https://github.com/angular/components/commit/918090a07642cfd16336d2b7f366dfb9830a5597) | fix | **listbox:** allow navigation to specific index ([#33485](https://github.com/angular/components/pull/33485)) | -### material +| [f5c586925e](https://github.com/angular/components/commit/f5c586925eb9a1cc00a9b2e22860be1f69ef9184) | fix | **select:** disable position locking ([#32772](https://github.com/angular/components/pull/32772)) | +### google-maps | Commit | Type | Description | | -- | -- | -- | -| [c86b024e3](https://github.com/angular/components/commit/c86b024e3f48b4a78aa6c3d041089683bfbba353) | fix | **core:** rework error state matcher to handle signal forms ([#33500](https://github.com/angular/components/pull/33500)) | +| [0870786036](https://github.com/angular/components/commit/0870786036286c3b0d3485cb31098afdf22c0350) | fix | fix mismatch in clusterer types ([#32778](https://github.com/angular/components/pull/32778)) | - -# 22.1.0-next.2 "22-1-0-next-2" (2026-07-01) + +# 21.1.5 "chlorine-car" (2026-02-18) ### aria | Commit | Type | Description | | -- | -- | -- | -| [e2cab48d6](https://github.com/angular/components/commit/e2cab48d68f61ccf967934c51e255734a94cd95d) | fix | **combobox:** improve autocomplete placeholder text color contrast ([#33465](https://github.com/angular/components/pull/33465)) | -| [8002bce0c](https://github.com/angular/components/commit/8002bce0c64883f5bc923d3b05663f66dd4beb80) | fix | **combobox:** zonejs compatibility ([#33453](https://github.com/angular/components/pull/33453)) | +| [017caa3be7](https://github.com/angular/components/commit/017caa3be77563aafa7fe2d964172559bfc7874b) | fix | **combobox:** fix autocomplete examples and add comments explaining combobox selection behavior ([#32714](https://github.com/angular/components/pull/32714)) | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [864dcadee](https://github.com/angular/components/commit/864dcadeeae85295d54b080e6da1335ced4d909c) | fix | **collections:** add ability to bulk select/deselect values ([#33469](https://github.com/angular/components/pull/33469)) | -| [918478727](https://github.com/angular/components/commit/9184787277cccc1da93026bfc549d2c7b073c99c) | fix | **scrolling:** reset more properties to avoid jumping during virtual scroll ([#33439](https://github.com/angular/components/pull/33439)) | +| [c825fa208d](https://github.com/angular/components/commit/c825fa208d0313429321dc66d680f09378488399) | fix | **tree:** no nodes focusable if data is replaced ([#32781](https://github.com/angular/components/pull/32781)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [73939a835](https://github.com/angular/components/commit/73939a835b49f9603cc8026eb373dbef5e9cb96a) | feat | **slide-toggle:** add full-width support ([#33424](https://github.com/angular/components/pull/33424)) | -| [d81320fad](https://github.com/angular/components/commit/d81320fadf92e5f865a851e6325cf94040b45197) | fix | **radio:** do not set cursor on non interactive radio ([#33418](https://github.com/angular/components/pull/33418)) | -| [ce4710970](https://github.com/angular/components/commit/ce47109701f2831add652533910e9105038a4138) | fix | **sidenav:** update inert value if mode changes ([#33461](https://github.com/angular/components/pull/33461)) | -| [58a78073f](https://github.com/angular/components/commit/58a78073fd88007a914e1f19d78d37fb2bff4eaa) | fix | **tabs:** validate animation duration ([#33452](https://github.com/angular/components/pull/33452)) | - - - - -# 22.0.3 "22-0-3" (2026-07-01) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [de9dd5909](https://github.com/angular/components/commit/de9dd59094fa0def103fac61e397b050c18c4735) | fix | **combobox:** improve autocomplete placeholder text color contrast ([#33465](https://github.com/angular/components/pull/33465)) | -| [e87216c14](https://github.com/angular/components/commit/e87216c141b45f358f28490120b64ab209f003ca) | fix | **combobox:** zonejs compatibility ([#33453](https://github.com/angular/components/pull/33453)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [3b74812ae](https://github.com/angular/components/commit/3b74812aeac536bd5b1ac4d119fa55964899ccf3) | fix | **collections:** add ability to bulk select/deselect values ([#33469](https://github.com/angular/components/pull/33469)) | -| [9e552a7b0](https://github.com/angular/components/commit/9e552a7b0c1e3dbb14e9b58697dc4e7d08b7d12a) | fix | **scrolling:** reset more properties to avoid jumping during virtual scroll ([#33439](https://github.com/angular/components/pull/33439)) | -### material +| [8f412ec5b7](https://github.com/angular/components/commit/8f412ec5b72b9fe6e8cb9be8956eb126a5f697c3) | fix | **select:** disable position locking ([#32772](https://github.com/angular/components/pull/32772)) | +### google-maps | Commit | Type | Description | | -- | -- | -- | -| [5e2a8525c](https://github.com/angular/components/commit/5e2a8525c413880fbd141c5c9cb4d0e7f35461c5) | fix | **radio:** do not set cursor on non interactive radio ([#33418](https://github.com/angular/components/pull/33418)) | -| [1c9bd0d6d](https://github.com/angular/components/commit/1c9bd0d6dcf66623d8d362d261fd5790e2262d27) | fix | **sidenav:** update inert value if mode changes ([#33461](https://github.com/angular/components/pull/33461)) | -| [a436cd44b](https://github.com/angular/components/commit/a436cd44b429300bfe4302bef418407019f75580) | fix | **tabs:** validate animation duration ([#33452](https://github.com/angular/components/pull/33452)) | +| [a73a70d291](https://github.com/angular/components/commit/a73a70d291f0dc8229241cd45ec8775b392cdc86) | fix | fix mismatch in clusterer types ([#32778](https://github.com/angular/components/pull/32778)) | - -# 22.1.0-next.1 "plastic scooter" (2026-06-17) + +# 21.2.0-next.4 "marzipan-rutabaga" (2026-02-11) ### aria | Commit | Type | Description | | -- | -- | -- | -| [31781a0246](https://github.com/angular/components/commit/31781a0246acacc7db7d8f77d502d2460e2cd832) | fix | **combobox:** allow setting readonly ([#33364](https://github.com/angular/components/pull/33364)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [231f94f555](https://github.com/angular/components/commit/231f94f5557bb58afc0070585688b3014d541eb2) | fix | **layout:** avoid CSS injection attacks in media matcher | -| [10db3e30f9](https://github.com/angular/components/commit/10db3e30f94c8cfd160679da7ba22e4c6c0dd8bb) | fix | **platform:** account for composedPath error during event replay ([#33409](https://github.com/angular/components/pull/33409)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [cd10a97a9d](https://github.com/angular/components/commit/cd10a97a9d2ae6cac888d72424d332207f9883cb) | fix | **bottom-sheet:** ensure animation event comes from container | -| [e49f1273c8](https://github.com/angular/components/commit/e49f1273c8f7de2deda7ab1d2162b5594d9b04fa) | fix | **chips:** correct focus management on chip destruction ([#33329](https://github.com/angular/components/pull/33329)) | -| [e31d3fd247](https://github.com/angular/components/commit/e31d3fd2477348bf9c47b39f05650735e6c4e536) | fix | **chips:** wrong padding when chip only has edit icon ([#33407](https://github.com/angular/components/pull/33407)) | -| [a02d935ca3](https://github.com/angular/components/commit/a02d935ca3f30f6302e04172b1ffb7dfd2e5522c) | fix | **grid-list:** always validate colspan | -| [0939437095](https://github.com/angular/components/commit/0939437095c14608b35edeec0312f6dcac45f917) | fix | **stepper:** validate animation durations | -### youtube-player -| Commit | Type | Description | -| -- | -- | -- | -| [2430e6e53b](https://github.com/angular/components/commit/2430e6e53bb797129a3ed0ee4a024ed647785b17) | fix | update to latest typings and fix error ([#33387](https://github.com/angular/components/pull/33387)) | -### multiple -| Commit | Type | Description | -| -- | -- | -- | -| [eb2c033311](https://github.com/angular/components/commit/eb2c033311977369573f864a5be06632d1cc66a1) | fix | improve dark theme visibility in menu, overlay, and portal examples ([#33367](https://github.com/angular/components/pull/33367)) | - - - - -# 22.0.2 "plastic lion" (2026-06-17) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [e8f3419060](https://github.com/angular/components/commit/e8f34190606a706e27b594ad84543fad62dd24e8) | fix | **layout:** avoid CSS injection attacks in media matcher | -| [9dc2b2b2ed](https://github.com/angular/components/commit/9dc2b2b2edefa0b01859ef68059d1a16a6837ecc) | fix | **platform:** account for composedPath error during event replay ([#33409](https://github.com/angular/components/pull/33409)) | +| [70f8b6aac](https://github.com/angular/components/commit/70f8b6aac471a20dcc8f51da7aefdc930469b185) | perf | **grid:** performance when selecting in a large grid ([#32766](https://github.com/angular/components/pull/32766)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [fb4478bff3](https://github.com/angular/components/commit/fb4478bff31976f6179fde21f800f1326395f707) | fix | **bottom-sheet:** ensure animation event comes from container | -| [e4f7f3498b](https://github.com/angular/components/commit/e4f7f3498b472badf60250ca1bf884e6b0f7a6aa) | fix | **chips:** correct focus management on chip destruction ([#33329](https://github.com/angular/components/pull/33329)) | -| [766b7aceee](https://github.com/angular/components/commit/766b7aceee027d6f40af15ab29d6fd47ebe1661b) | fix | **chips:** wrong padding when chip only has edit icon ([#33407](https://github.com/angular/components/pull/33407)) | -| [ebca801ee5](https://github.com/angular/components/commit/ebca801ee55b9f63a3b5b29cc6113e09ff66ffe2) | fix | **grid-list:** always validate colspan | -| [30942bcd36](https://github.com/angular/components/commit/30942bcd3640efd7f3980044144dd1fc8b10e8f3) | fix | **stepper:** validate animation durations | +| [4ff346bbb](https://github.com/angular/components/commit/4ff346bbb56bb8f6b117c9d03b6f80eac32bc2f8) | fix | **radio:** hide empty labels ([#32754](https://github.com/angular/components/pull/32754)) | +| [bee55117c](https://github.com/angular/components/commit/bee55117c674ac1ab3ec44acc624e92d1f960e14) | fix | **slide-toggle:** overly broad selector ([#32740](https://github.com/angular/components/pull/32740)) | +| [06750e02e](https://github.com/angular/components/commit/06750e02e42283f7cdf84772da76ef3d9426b5ab) | fix | **tooltip:** do not block events to other overlays | ### multiple | Commit | Type | Description | | -- | -- | -- | -| [2995797ded](https://github.com/angular/components/commit/2995797ded3713909d9742c4032f4f8647193cb3) | fix | improve dark theme visibility in menu, overlay, and portal examples ([#33367](https://github.com/angular/components/pull/33367)) | +| [529c1bfd7](https://github.com/angular/components/commit/529c1bfd75ddb1e80136d509fdf336f9efc2a1bc) | fix | change aria keyboard manager to only handle repeated events in correct places ([#32728](https://github.com/angular/components/pull/32728)) | - -# 22.1.0-next.0 "argon-pineapple" (2026-06-10) -No user facing changes in this release - - - - -# 22.0.1 "argon-apple" (2026-06-10) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [7581b0592](https://github.com/angular/components/commit/7581b0592b6aef617e3563e206b0645d3122d68c) | fix | **combobox:** avoid error for synthetic events ([#33360](https://github.com/angular/components/pull/33360)) | -| [1c4706155](https://github.com/angular/components/commit/1c4706155d6f8a5d465dbb2b6a40cf7da2efdf17) | fix | **combobox:** prevent re-dispatching keyboard event on control target change ([#33362](https://github.com/angular/components/pull/33362)) | -| [96e9ce10c](https://github.com/angular/components/commit/96e9ce10c35ea3df9a9604a5ab3b5c85e8cc263e) | fix | **tree:** recursive textDirection getter ([#33337](https://github.com/angular/components/pull/33337)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [629aea403](https://github.com/angular/components/commit/629aea4032cedf9428e3ed4f729d6a5836fa84de) | fix | **a11y:** avoid prototype conflicts in id generator ([#33356](https://github.com/angular/components/pull/33356)) | -| [49aeb676c](https://github.com/angular/components/commit/49aeb676cd82d84f00983c9959d68199115c31e0) | fix | **clipboard:** avoid infinite attempt loop ([#33366](https://github.com/angular/components/pull/33366)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [d7a8cb963](https://github.com/angular/components/commit/d7a8cb9633f2f670a6bf455a9f51d492b01cb68c) | fix | **dialog:** ignore clicks on aria-disabled close buttons ([#33373](https://github.com/angular/components/pull/33373)) | -| [bde3c7621](https://github.com/angular/components/commit/bde3c7621dddba2932f23743fc67a18feec664fe) | fix | **timepicker:** do not allow intervals less than a second ([#33354](https://github.com/angular/components/pull/33354)) | -### youtube-player -| Commit | Type | Description | -| -- | -- | -- | -| [d75a22d69](https://github.com/angular/components/commit/d75a22d69ddc9ffc5afa6b677f39fbbfc3ad5f7f) | fix | avoid errors with clobbered variables | -| [fe0a96ce6](https://github.com/angular/components/commit/fe0a96ce673c3622e87a7a44bff8f40a1ad70675) | fix | validate ID before attaching them to placeholder | - - - - -# 22.0.0 "aurostibite-ambulance" (2026-06-03) -## Breaking Changes + +# 21.1.4 "zircaloy-gondola" (2026-02-11) ### aria -- The legacy combobox and autocomplete implementations have been removed. Use the new standalone combobox instead. - - * feat(aria/combobox): promote simple-combobox to stable un-prefixed combobox - - - Relocates public, private, and example directories to clean `combobox` entry points. - - Renames internal layout symbols, selectors, and uppercase tokens (`SIMPLE_COMBOBOX_POPUP` -> `COMBOBOX_POPUP`). - - Establishes full documentation extraction parity with the `json_api` Bazel rule target. - - Standardizes the accompanying toolbar component showcase into the clean `aria-toolbar` path. - - Re-routes dev-app navigation links and migrates public API golden records. -- `SimpleCombobox` has been promoted to `Combobox`. All `simple-combobox` prefixed symbols, selectors, and tokens have been renamed to use the `combobox` prefix. - - * refactor(aria/combobox): relocate and restructure autocomplete and toolbar examples - Relocate the autocomplete examples to `src/components-examples/aria/autocomplete` and toolbar examples to `src/components-examples/aria/toolbar`. - - Restore naming continuity with the historical codebase by stripping redundant prefixes from example filenames and component selectors. - - Sync dev-app preview routing layout paths and strict Bazel target dependency links. -### cdk -- * `CDK_DESCRIBEDBY_HOST_ATTRIBUTE` has been removed. - * `CDK_DESCRIBEDBY_ID_PREFIX` has been removed. - * The `injector` parameter of the `ConfigurableFocusTrap` and `FocusTrap` constructors is now required. - * The boolean parameter of `ConfigurableFocusTrapFactory.create` has been replaced with a config object. - * `MESSAGES_CONTAINER_ID` has been removed. -- * The `event` parameter of `DropListRef.drop` is now required. -- * `ContextMenuTracker` has been renamed to `MenuTracker`. -### material -- * `MatListOption.checkboxPosition` has been removed. use `togglePosition` instead. - * `MatListOptionCheckboxPosition` has been renamed to `MatListOptionTogglePosition`. -- * `ArrowViewState` has been removed. - * `ArrowViewStateTransition` has been removed. -### multiple -- * A bunch of constructors that with rest arguments have been removed. If you were extending Material/CDK components, you may have to update your `super` calls accordingly. -- Renames the values input/model to value in Combobox, Listbox, Tree, Menu, Toolbar, and Select. Users must update their templates to use the value property instead of values. - - * refactor(multiple): update api goldens -### google-maps | Commit | Type | Description | | -- | -- | -- | -| [e44ff8318](https://github.com/angular/components/commit/e44ff8318911927624d90e0d2fcba6ee94ec5ff7) | feat | Add support for the gmp-click event ([#33147](https://github.com/angular/components/pull/33147)) | -| [b8201edee](https://github.com/angular/components/commit/b8201edeef62258c3d1e1ebeaf8ee14766918c22) | fix | deprecate heatmap layer ([#33208](https://github.com/angular/components/pull/33208)) | +| [0bd0ad639](https://github.com/angular/components/commit/0bd0ad6397dd4bbedd58a6df189c9dfbcec037b5) | perf | **grid:** performance when selecting in a large grid ([#32766](https://github.com/angular/components/pull/32766)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [867ba993b](https://github.com/angular/components/commit/867ba993b3baf35868d7c898e77289870316bbb9) | feat | **bottom-sheet:** add the ability to pass bindings | -| [b4a89d599](https://github.com/angular/components/commit/b4a89d5996864e591cfac762db420ec591d931e2) | feat | **button:** Add support for showing a progress indicator inside the button ([#32698](https://github.com/angular/components/pull/32698)) | -| [a46b0a1d4](https://github.com/angular/components/commit/a46b0a1d4200bf74b036c25ee477d22025f444c8) | feat | **core:** add mixins for Material Design typography ([#32959](https://github.com/angular/components/pull/32959)) | -| [bf3596b53](https://github.com/angular/components/commit/bf3596b53ba1cf118ec06343f8a7772e0fb0e55d) | feat | **dialog:** add the ability to pass bindings | -| [85c16fe4b](https://github.com/angular/components/commit/85c16fe4bb68d89d1f304350ecf965aeceea4662) | feat | **tabs:** add support for separate tab animation durations ([#32869](https://github.com/angular/components/pull/32869)) | -| [440cb1606](https://github.com/angular/components/commit/440cb16064a4daf50437e806057403cf80c52bd6) | fix | **autocomplete:** remove modal workaround | -| [21f8bbbf2](https://github.com/angular/components/commit/21f8bbbf226c1938c46fe2afcc4d8d070ec33c86) | fix | **badge:** allow badge defaults to be configured ([#33312](https://github.com/angular/components/pull/33312)) | -| [07c2d002d](https://github.com/angular/components/commit/07c2d002de52de1691410543a83ffffd7a0b373b) | fix | **core:** address sass compiler warnings ([#33040](https://github.com/angular/components/pull/33040)) | -| [add8f16c0](https://github.com/angular/components/commit/add8f16c01ad683b9e2ab9e1bf5ad3a984dd9921) | fix | **list:** breaking changes for v22 | -| [31904510b](https://github.com/angular/components/commit/31904510be852961916283d830ae80167ee52dbb) | fix | **menu:** close menu when cleared from trigger ([#33306](https://github.com/angular/components/pull/33306)) | -| [9d73c98b5](https://github.com/angular/components/commit/9d73c98b57d46454d51344bbd59055bc06dabc6b) | fix | **menu:** missing panelClass getter ([#33191](https://github.com/angular/components/pull/33191)) | -| [348c3c89d](https://github.com/angular/components/commit/348c3c89d511ab5f74fda95ef0f0ae7a332527fb) | fix | **select:** remove modal workaround | -| [f1a435508](https://github.com/angular/components/commit/f1a435508ada291b580e20e628464f552288690d) | fix | **sidenav:** handle mixed sidenav and drawer ([#33274](https://github.com/angular/components/pull/33274)) | -| [c31619852](https://github.com/angular/components/commit/c3161985279bf49f9aae55b732fdd3b2872e1f7e) | fix | **sidenav:** mark content as inert while open | -| [a4d92c5fc](https://github.com/angular/components/commit/a4d92c5fcb7d163ec8dd180815175bbaa0a38491) | fix | **sidenav:** more robust reset logic for inert attribute ([#33257](https://github.com/angular/components/pull/33257)) | -| [c2f1c5b03](https://github.com/angular/components/commit/c2f1c5b035795079b155b9e60a290abe4d4c9537) | fix | **sidenav:** query not resolving | -| [75718e4fb](https://github.com/angular/components/commit/75718e4fbebce2a7f8aa30550a12d58a5a6f9b93) | fix | **sort:** breaking changes for v22 | -| [6ed6218c4](https://github.com/angular/components/commit/6ed6218c474bfe4578b17d2391d8677fe92671cc) | fix | **tabs:** incorrect animation variable name ([#32941](https://github.com/angular/components/pull/32941)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [1a5d5d101](https://github.com/angular/components/commit/1a5d5d101aa0c3d3113ca8e77f0dbe4175afe655) | feat | **dialog:** add the ability to pass bindings | -| [24115c021](https://github.com/angular/components/commit/24115c02189ca0b1b3f09a909b8962d330f55f91) | feat | **portal:** add directives support to ComponentPortal ([#33142](https://github.com/angular/components/pull/33142)) | -| [7426334c5](https://github.com/angular/components/commit/7426334c5efb76ac3f359e26a7f6e48e29d4ec78) | fix | **a11y:** breaking changes for v22 | -| [81c6bbd89](https://github.com/angular/components/commit/81c6bbd89da765a3ce8fd9dd8fd0b36b8eac4ca4) | fix | **drag-drop:** breaking changes for v22 | -| [ffb23f6f8](https://github.com/angular/components/commit/ffb23f6f825a0e86cd6d9bf3505af70b2264e588) | fix | **menu:** breaking changes for v22 | -| [4c298970e](https://github.com/angular/components/commit/4c298970ed67abfa7b9980e219bbbafe3c71ddc5) | fix | **scrolling:** make it easier to provide custom scrollable ([#33269](https://github.com/angular/components/pull/33269)) | -| [aa42b7798](https://github.com/angular/components/commit/aa42b7798b23dfa94b96ac217d8139d9cfa86c55) | fix | **table:** expose rendered rows ([#33304](https://github.com/angular/components/pull/33304)) | -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [d91f46b4c](https://github.com/angular/components/commit/d91f46b4c522159e025d3b94b653981f344a4fdf) | feat | **accordion:** introduce accordion harness ([#33046](https://github.com/angular/components/pull/33046)) | -| [e3d84f2e0](https://github.com/angular/components/commit/e3d84f2e0adb9254d804c10ba24e6c4528efad53) | feat | **combobox:** add test harnesses ([#33194](https://github.com/angular/components/pull/33194)) | -| [0ca47b4a0](https://github.com/angular/components/commit/0ca47b4a060595919fd2b2cd62d0097071c4795d) | feat | **combobox:** migrate simple-combobox directly into primary entrypoints ([#33206](https://github.com/angular/components/pull/33206)) | -| [6ec07bc0c](https://github.com/angular/components/commit/6ec07bc0cbe39be48faaf6021caecf2d0037df1f) | feat | **grid:** add test harnesses ([#33081](https://github.com/angular/components/pull/33081)) | -| [1885d3534](https://github.com/angular/components/commit/1885d35346e05341532aed56ac476f9a9eae179b) | feat | **listbox:** introduce listbox harness ([#33064](https://github.com/angular/components/pull/33064)) | -| [75fae5275](https://github.com/angular/components/commit/75fae5275c5702e64477da6a2ac8a14428623c8b) | feat | **menu:** introduce menu harness ([#33067](https://github.com/angular/components/pull/33067)) | -| [c25e6252e](https://github.com/angular/components/commit/c25e6252ecf3b5a6472151a8fca4af093b3322e6) | feat | **tabs:** add test harnesses ([#33079](https://github.com/angular/components/pull/33079)) | -| [a49508bac](https://github.com/angular/components/commit/a49508bacf419f2e8f0545158de1f951f3ba1c1a) | feat | **toolbar:** add test harnesses ([#33068](https://github.com/angular/components/pull/33068)) | -| [30f223972](https://github.com/angular/components/commit/30f223972870c3016e8e14f4bafb913d22117e20) | feat | **tree:** add test harnesses ([#33066](https://github.com/angular/components/pull/33066)) | -| [91a4932f6](https://github.com/angular/components/commit/91a4932f65e02ce7087cb38cc0198b6f1f993242) | fix | **combobox:** increases autocomplete demo's placeholder text c… ([#33084](https://github.com/angular/components/pull/33084)) | -| [218a77cf9](https://github.com/angular/components/commit/218a77cf946808ca966c131f56c393ef91c662b3) | fix | **combobox:** separates placeholder prefixes ([#33163](https://github.com/angular/components/pull/33163)) | -| [ce1d9a728](https://github.com/angular/components/commit/ce1d9a7286b66e2c9511183586fceabf2a925c02) | fix | **menu:** allow menu item role override ([#33264](https://github.com/angular/components/pull/33264)) | -| [196b7064d](https://github.com/angular/components/commit/196b7064db336830ee9c43244c00631935dd2c55) | fix | **menu:** defer menu item focus in case menus in cdk overlay ([#33258](https://github.com/angular/components/pull/33258)) | -| [6443b79f9](https://github.com/angular/components/commit/6443b79f9a629c67766675ebd740722eb65b2926) | fix | **menu:** unable to set softDisabled ([#33265](https://github.com/angular/components/pull/33265)) | +| [84b5dd766](https://github.com/angular/components/commit/84b5dd766a7a7c293614bd0b792d35bffccefb2a) | fix | **radio:** hide empty labels ([#32754](https://github.com/angular/components/pull/32754)) | +| [58870b511](https://github.com/angular/components/commit/58870b5117ef910b5ae3c6fc2ea6cd6e2f712aa2) | fix | **slide-toggle:** overly broad selector ([#32740](https://github.com/angular/components/pull/32740)) | +| [d175b0180](https://github.com/angular/components/commit/d175b0180e7e2b35cf7c10db3b76da394c8b35f4) | fix | **tooltip:** do not block events to other overlays | ### multiple | Commit | Type | Description | | -- | -- | -- | -| [6cb6b5ee1](https://github.com/angular/components/commit/6cb6b5ee1980c18c4ebec9337513e3b6a863661f) | fix | make more public APIs readonly ([#33071](https://github.com/angular/components/pull/33071)) | -| [a88904279](https://github.com/angular/components/commit/a88904279086a29deb84f34f4fadf9e69fbf3960) | fix | prevent form submissions in aria directives ([#33297](https://github.com/angular/components/pull/33297)) | -| [bb4f8ec50](https://github.com/angular/components/commit/bb4f8ec50407500ba4d1aa131bcd3c828cad67ae) | fix | re-export collection util ([#33171](https://github.com/angular/components/pull/33171)) | -| [84f2afd24](https://github.com/angular/components/commit/84f2afd24933c8742662ee6d12a6f3f98f5dea9e) | fix | remove developer preview tag from aria ([#33232](https://github.com/angular/components/pull/33232)) | -| [ce4c2c0a1](https://github.com/angular/components/commit/ce4c2c0a119e44bbeee0f4e4664a4a5e40820863) | fix | remove empty constructors ([#33048](https://github.com/angular/components/pull/33048)) | -| [936f1148b](https://github.com/angular/components/commit/936f1148b3d6951bcfa1ce5e05538e8b746b677c) | fix | use eager change detection | -| [94a50a25f](https://github.com/angular/components/commit/94a50a25f95c0e28d661b957a0a17501cc9998f9) | refactor | rename values to value for signal forms compatibility ([#33012](https://github.com/angular/components/pull/33012)) | - - - - -# 21.2.14 "amicite-atlas" (2026-06-03) -No user facing changes in this release - - - - -# 21.2.13 "21-2-13" (2026-05-27) -No user facing changes in this release - - - - -# 21.2.12 "plastic-moose" (2026-05-20) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [da87be7646](https://github.com/angular/components/commit/da87be76464d76ec11ae922abd5f4c72c5b4ea3e) | fix | **datepicker:** ensure dates don't overflow on a small screen ([#33281](https://github.com/angular/components/pull/33281)) | - - - - -# 21.2.11 "crystal ball" (2026-05-13) -No user facing changes in this release - - - - -# 21.2.10 "metal-wallaby" (2026-05-06) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [48973661e](https://github.com/angular/components/commit/48973661ec50649918e3dcafc096d340219135da) | fix | **menu:** do not set default aria-label ([#33202](https://github.com/angular/components/pull/33202)) | - - - - -# 21.2.9 "astatine-astronaut" (2026-04-29) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [bf14cc9d9](https://github.com/angular/components/commit/bf14cc9d9fd349974cba853d4747b6859b3aae5b) | fix | **menu:** use computed for menu item patterns, with trigger on visible ([#33118](https://github.com/angular/components/pull/33118)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [3a3852d45](https://github.com/angular/components/commit/3a3852d45a4bbcfb23b46ca00e0eba1d70081ba3) | fix | **tree:** enter/space key on child node should not toggle parent node expansion ([#33125](https://github.com/angular/components/pull/33125)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [51271c619](https://github.com/angular/components/commit/51271c6194a9b55ee847c566a8960c9be5ec7cce) | fix | **stepper:** allow stepper to be labelled ([#33137](https://github.com/angular/components/pull/33137)) | - - - - -# 21.2.8 "v21-2-8" (2026-04-22) -No user facing changes in this release - - - - -# 21.2.7 "carbon-jetski" (2026-04-16) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [5954c4594c](https://github.com/angular/components/commit/5954c4594c6df87908bc7849214a29f6897b213e) | fix | **sort:** deprecate MatSortHeaderIntl and hide from docs ([#33089](https://github.com/angular/components/pull/33089)) | - - - - -# 21.2.6 "steel bottle" (2026-04-08) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [5b4bbe9c4f](https://github.com/angular/components/commit/5b4bbe9c4f8c902d6923b139e7a925822c899bc1) | fix | **select:** wrong transform origin when opening upwards inside another overlay ([#33032](https://github.com/angular/components/pull/33032)) | - - - - -# 21.2.5 "steel-wombat" (2026-04-01) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [42e10553c](https://github.com/angular/components/commit/42e10553cfcf205fea44522ae631fe23f90b03ea) | fix | **stepper:** linear updates not reflected in the DOM ([#33007](https://github.com/angular/components/pull/33007)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [895908a12](https://github.com/angular/components/commit/895908a12dd81d889f95961da59e80823faf8889) | fix | **sidenav:** not resetting margin if transition does not start ([#33001](https://github.com/angular/components/pull/33001)) | -| [475a8aabb](https://github.com/angular/components/commit/475a8aabb30044c4cdca1bdb0b1de832db6e5936) | fix | **slider:** not picking up static direction ([#33006](https://github.com/angular/components/pull/33006)) | -| [8a5ed09e5](https://github.com/angular/components/commit/8a5ed09e59e312f82b969b19ae4d077e4574bd12) | fix | **tooltip:** allow hover detection logic to be customized ([#33018](https://github.com/angular/components/pull/33018)) | - - - - -# 21.2.4 "hassium-halfpipe" (2026-03-25) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [87aa6d977](https://github.com/angular/components/commit/87aa6d977166d09b0f321eed6300663bfe57b8aa) | fix | **tooltip:** wrong type for shape token ([#32968](https://github.com/angular/components/pull/32968)) | - - - - -# 21.2.3 "21-2-3" (2026-03-18) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [688deda58](https://github.com/angular/components/commit/688deda580c9141e2a7e36164dbba860d060e5df) | fix | **drag-drop:** item returned to wrong index in initial container ([#32944](https://github.com/angular/components/pull/32944)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [917526511](https://github.com/angular/components/commit/917526511e1d52bb2d9fa665e10f087e62aa2229) | fix | **datepicker:** add harness for datepicker actions ([#32925](https://github.com/angular/components/pull/32925)) | +| [2a269598a](https://github.com/angular/components/commit/2a269598af0fc30cae41378cb2a92112738c26fc) | fix | change aria keyboard manager to only handle repeated events in correct places ([#32728](https://github.com/angular/components/pull/32728)) | - -# 21.2.2 "plastic-goose" (2026-03-11) + +# 21.2.0-next.3 "wooden-kiwi" (2026-02-04) ### aria | Commit | Type | Description | | -- | -- | -- | -| [345c543dcb](https://github.com/angular/components/commit/345c543dcb8cf5869930ffdec37a3101483eddd8) | fix | **grid:** unit tests and small fixes for grid directives ([#32841](https://github.com/angular/components/pull/32841)) | - - - - -# 21.3.0-next.0 "glass heels" (2026-03-04) +| [31ff7f26b](https://github.com/angular/components/commit/31ff7f26b5dd08c1d48dc5ebb8f1a9ba57a2dc9c) | fix | **grid:** align output naming with styleguide | +| [f1157b1c8](https://github.com/angular/components/commit/f1157b1c88b78b87ae6e16a74ff2445aeb4156bf) | fix | **menu:** Add label property for proper aria-label ([#32710](https://github.com/angular/components/pull/32710)) | +| [d030725f4](https://github.com/angular/components/commit/d030725f462d93bbca9918f153a48b5631ef0476) | fix | **menu:** align output naming with styleguide | +| [06e3d756c](https://github.com/angular/components/commit/06e3d756ca1114ee05d785cd38a7e144795b709e) | fix | **toolbar:** show outline on selected items when forced colors active ([#32708](https://github.com/angular/components/pull/32708)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [b4a89d5996](https://github.com/angular/components/commit/b4a89d5996864e591cfac762db420ec591d931e2) | feat | **button:** Add support for showing a progress indicator inside the button ([#32698](https://github.com/angular/components/pull/32698)) | -| [85c16fe4bb](https://github.com/angular/components/commit/85c16fe4bb68d89d1f304350ecf965aeceea4662) | feat | **tabs:** add support for separate tab animation durations ([#32869](https://github.com/angular/components/pull/32869)) | -### multiple +| [a3f2ec8a9](https://github.com/angular/components/commit/a3f2ec8a94aadf459797114d31ba487741b37d35) | fix | **badge:** move warning check later ([#32724](https://github.com/angular/components/pull/32724)) | +| [865e3d50c](https://github.com/angular/components/commit/865e3d50ca86b237cdf0c78ca411164dfb0a85a1) | fix | **chips:** Example violates axe accessibility tests ([#32679](https://github.com/angular/components/pull/32679)) | +### cdk-experimental | Commit | Type | Description | | -- | -- | -- | -| [936f1148b3](https://github.com/angular/components/commit/936f1148b3d6951bcfa1ce5e05538e8b746b677c) | fix | use eager change detection | +| [6fd833d2b](https://github.com/angular/components/commit/6fd833d2bcf1c0745ddff8fadb63210c31cf8b07) | perf | **column-resize:** add debounce to column header hover to prevent excessive handler rendering ([#30709](https://github.com/angular/components/pull/30709)) | - -# 21.2.1 "leather heels" (2026-03-04) + +# 21.1.3 "wooden-duck" (2026-02-04) ### aria | Commit | Type | Description | | -- | -- | -- | -| [c1295c5818](https://github.com/angular/components/commit/c1295c5818c820ab182727b4f85e5f55a05f6385) | fix | **menu:** itemSelected not emitted when menu is attached to a trigger ([#32880](https://github.com/angular/components/pull/32880)) | +| [4186f740f](https://github.com/angular/components/commit/4186f740faf9b9510002fd2ddab13d49770a7bc5) | fix | **menu:** Add label property for proper aria-label ([#32710](https://github.com/angular/components/pull/32710)) | +| [d72693582](https://github.com/angular/components/commit/d72693582b7b8947f30d20824d07eb5a12365542) | fix | **toolbar:** show outline on selected items when forced colors active ([#32708](https://github.com/angular/components/pull/32708)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [cebf303417](https://github.com/angular/components/commit/cebf303417678ea4f48e32f17e55d6649da04a27) | fix | **checkbox:** hide decorative elements from assistive technology | -| [2abe8e6b73](https://github.com/angular/components/commit/2abe8e6b7330f5281b560b202ab70bdf593782cc) | fix | **menu:** render backdrop for context menu if explicitly opted in ([#32845](https://github.com/angular/components/pull/32845)) | -| [e2f7308dc4](https://github.com/angular/components/commit/e2f7308dc4b5e539c2ccacf138a7997f9f5fdf03) | fix | **radio:** hide decorative elements from assistive technology | -### multiple +| [c4f745ce7](https://github.com/angular/components/commit/c4f745ce7aa3b6ef13cd181037ae1076f6e957fa) | fix | **badge:** move warning check later ([#32724](https://github.com/angular/components/pull/32724)) | +| [910aed3c8](https://github.com/angular/components/commit/910aed3c88717d87e9942cc33ea207e2f6825abf) | fix | **chips:** Example violates axe accessibility tests ([#32679](https://github.com/angular/components/pull/32679)) | +### cdk-experimental | Commit | Type | Description | | -- | -- | -- | -| [17c19cc73b](https://github.com/angular/components/commit/17c19cc73b4e7e24ae5579c32ae5a784d92c2a12) | fix | expand type for restoreFocus ([#32877](https://github.com/angular/components/pull/32877)) | +| [591516c93](https://github.com/angular/components/commit/591516c9312f2f710bbcac63955b817f50e6ef84) | perf | **column-resize:** add debounce to column header hover to prevent excessive handler rendering ([#30709](https://github.com/angular/components/pull/30709)) | - -# 21.2.0 "plastic-lion" (2026-02-25) + +# 21.2.0-next.2 "plastic-bottle" (2026-01-29) ### aria | Commit | Type | Description | | -- | -- | -- | -| [31ff7f26b5](https://github.com/angular/components/commit/31ff7f26b5dd08c1d48dc5ebb8f1a9ba57a2dc9c) | fix | **grid:** align output naming with styleguide | -| [d030725f46](https://github.com/angular/components/commit/d030725f462d93bbca9918f153a48b5631ef0476) | fix | **menu:** align output naming with styleguide | +| [82a51f367b](https://github.com/angular/components/commit/82a51f367b2741245f77ff694a1a894910cdbdea) | fix | **combobox:** Add announcement for empty results to autocomplete demo ([#32707](https://github.com/angular/components/pull/32707)) | +| [0395ca20f4](https://github.com/angular/components/commit/0395ca20f4cd0bf563b63aa412e1b4956e384f9b) | perf | **grid:** avoid excessive change detections ([#32705](https://github.com/angular/components/pull/32705)) | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [cd5209e9f5](https://github.com/angular/components/commit/cd5209e9f59c8bd31de29d03fd743452d8ae3d03) | fix | **portal:** allow bindings to be passed to component portal ([#32814](https://github.com/angular/components/pull/32814)) | -| [e397ecbcfa](https://github.com/angular/components/commit/e397ecbcfad4e85fde8d116faf5caa80dcddca45) | fix | **table:** remove string-based DI tokens ([#32677](https://github.com/angular/components/pull/32677)) | +| [c71256dfdb](https://github.com/angular/components/commit/c71256dfdb981a628120d6bbafdc3001836dd9a8) | fix | **listbox:** incorrect wording in error message ([#32697](https://github.com/angular/components/pull/32697)) | +| [e78587f2bf](https://github.com/angular/components/commit/e78587f2bfc87abf06a78cc95da201700b14a962) | fix | **table:** throw when multiple row templates are used with virtual scrolling ([#32682](https://github.com/angular/components/pull/32682)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [8c4526935e](https://github.com/angular/components/commit/8c4526935ef7e960ed4245a4621d41065ba34e3b) | fix | **core:** expose strong focus indicator structural styles ([#32808](https://github.com/angular/components/pull/32808)) | -| [7fe3081148](https://github.com/angular/components/commit/7fe308114829c4ce454cc247e59e59387ee7fd17) | fix | **datepicker:** drop dependency on NgClass ([#32810](https://github.com/angular/components/pull/32810)) | - - - - -# 21.1.6 "plastic-monkey" (2026-02-25) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [50c34e6ef3](https://github.com/angular/components/commit/50c34e6ef314177cdd725a5b0c3177b65cc2809a) | fix | **combobox:** Adjust AutoComplete examples to properly announce no results text ([#32786](https://github.com/angular/components/pull/32786)) | +| [c70a4737a9](https://github.com/angular/components/commit/c70a4737a9fbe398c535be68bf1bcd625a52d8d2) | fix | **sidenav:** incorrectly trapping focus in some cases ([#32699](https://github.com/angular/components/pull/32699)) | +| [34bcffb9f5](https://github.com/angular/components/commit/34bcffb9f53074b1f9b2a0a2491da1774612b97b) | fix | **timepicker:** make it easier to style timepicker panel ([#32711](https://github.com/angular/components/pull/32711)) | - -# 21.1.5 "chlorine-car" (2026-02-18) + +# 21.1.2 "glass-bottle" (2026-01-28) ### aria | Commit | Type | Description | | -- | -- | -- | -| [017caa3be7](https://github.com/angular/components/commit/017caa3be77563aafa7fe2d964172559bfc7874b) | fix | **combobox:** fix autocomplete examples and add comments explaining combobox selection behavior ([#32714](https://github.com/angular/components/pull/32714)) | +| [1d9918a9de](https://github.com/angular/components/commit/1d9918a9dea48c1d4349233dc8a0d5cee7c48ea8) | fix | **combobox:** Add announcement for empty results to autocomplete demo ([#32707](https://github.com/angular/components/pull/32707)) | +| [81b407d353](https://github.com/angular/components/commit/81b407d353ab8a8b52447d8e2ec9c2d3c4471a1a) | perf | **grid:** avoid excessive change detections ([#32705](https://github.com/angular/components/pull/32705)) | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [c825fa208d](https://github.com/angular/components/commit/c825fa208d0313429321dc66d680f09378488399) | fix | **tree:** no nodes focusable if data is replaced ([#32781](https://github.com/angular/components/pull/32781)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [8f412ec5b7](https://github.com/angular/components/commit/8f412ec5b72b9fe6e8cb9be8956eb126a5f697c3) | fix | **select:** disable position locking ([#32772](https://github.com/angular/components/pull/32772)) | -### google-maps -| Commit | Type | Description | -| -- | -- | -- | -| [a73a70d291](https://github.com/angular/components/commit/a73a70d291f0dc8229241cd45ec8775b392cdc86) | fix | fix mismatch in clusterer types ([#32778](https://github.com/angular/components/pull/32778)) | - - - - -# 21.1.4 "zircaloy-gondola" (2026-02-11) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [0bd0ad639](https://github.com/angular/components/commit/0bd0ad6397dd4bbedd58a6df189c9dfbcec037b5) | perf | **grid:** performance when selecting in a large grid ([#32766](https://github.com/angular/components/pull/32766)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [84b5dd766](https://github.com/angular/components/commit/84b5dd766a7a7c293614bd0b792d35bffccefb2a) | fix | **radio:** hide empty labels ([#32754](https://github.com/angular/components/pull/32754)) | -| [58870b511](https://github.com/angular/components/commit/58870b5117ef910b5ae3c6fc2ea6cd6e2f712aa2) | fix | **slide-toggle:** overly broad selector ([#32740](https://github.com/angular/components/pull/32740)) | -| [d175b0180](https://github.com/angular/components/commit/d175b0180e7e2b35cf7c10db3b76da394c8b35f4) | fix | **tooltip:** do not block events to other overlays | -### multiple -| Commit | Type | Description | -| -- | -- | -- | -| [2a269598a](https://github.com/angular/components/commit/2a269598af0fc30cae41378cb2a92112738c26fc) | fix | change aria keyboard manager to only handle repeated events in correct places ([#32728](https://github.com/angular/components/pull/32728)) | - - - - -# 21.1.3 "wooden-duck" (2026-02-04) -### aria -| Commit | Type | Description | -| -- | -- | -- | -| [4186f740f](https://github.com/angular/components/commit/4186f740faf9b9510002fd2ddab13d49770a7bc5) | fix | **menu:** Add label property for proper aria-label ([#32710](https://github.com/angular/components/pull/32710)) | -| [d72693582](https://github.com/angular/components/commit/d72693582b7b8947f30d20824d07eb5a12365542) | fix | **toolbar:** show outline on selected items when forced colors active ([#32708](https://github.com/angular/components/pull/32708)) | +| [9b54cb20da](https://github.com/angular/components/commit/9b54cb20daff4faaae64e882da7efccd66c7cc28) | fix | **listbox:** incorrect wording in error message ([#32697](https://github.com/angular/components/pull/32697)) | +| [4b8fc71c08](https://github.com/angular/components/commit/4b8fc71c08024cbac0f6f5221ecbd5ae2263f065) | fix | **table:** throw when multiple row templates are used with virtual scrolling ([#32682](https://github.com/angular/components/pull/32682)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [c4f745ce7](https://github.com/angular/components/commit/c4f745ce7aa3b6ef13cd181037ae1076f6e957fa) | fix | **badge:** move warning check later ([#32724](https://github.com/angular/components/pull/32724)) | -| [910aed3c8](https://github.com/angular/components/commit/910aed3c88717d87e9942cc33ea207e2f6825abf) | fix | **chips:** Example violates axe accessibility tests ([#32679](https://github.com/angular/components/pull/32679)) | -### cdk-experimental -| Commit | Type | Description | -| -- | -- | -- | -| [591516c93](https://github.com/angular/components/commit/591516c9312f2f710bbcac63955b817f50e6ef84) | perf | **column-resize:** add debounce to column header hover to prevent excessive handler rendering ([#30709](https://github.com/angular/components/pull/30709)) | +| [bb69f3ca7d](https://github.com/angular/components/commit/bb69f3ca7daeeaaf30c4c86310c504f9d2925bd5) | fix | **sidenav:** incorrectly trapping focus in some cases ([#32699](https://github.com/angular/components/pull/32699)) | +| [7824225a5b](https://github.com/angular/components/commit/7824225a5b5e9911867e378b8b7529ec852baca2) | fix | **timepicker:** make it easier to style timepicker panel ([#32711](https://github.com/angular/components/pull/32711)) | - -# 21.1.2 "glass-bottle" (2026-01-28) + +# 21.2.0-next.1 "plastic-ant" (2026-01-21) ### aria | Commit | Type | Description | | -- | -- | -- | -| [1d9918a9de](https://github.com/angular/components/commit/1d9918a9dea48c1d4349233dc8a0d5cee7c48ea8) | fix | **combobox:** Add announcement for empty results to autocomplete demo ([#32707](https://github.com/angular/components/pull/32707)) | -| [81b407d353](https://github.com/angular/components/commit/81b407d353ab8a8b52447d8e2ec9c2d3c4471a1a) | perf | **grid:** avoid excessive change detections ([#32705](https://github.com/angular/components/pull/32705)) | +| [ec384a37ab](https://github.com/angular/components/commit/ec384a37ab7df3838eaf7fcc238aab0ce8ba5ef2) | fix | **grid:** unit tests for grid pattern ([#32633](https://github.com/angular/components/pull/32633)) | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [9b54cb20da](https://github.com/angular/components/commit/9b54cb20daff4faaae64e882da7efccd66c7cc28) | fix | **listbox:** incorrect wording in error message ([#32697](https://github.com/angular/components/pull/32697)) | -| [4b8fc71c08](https://github.com/angular/components/commit/4b8fc71c08024cbac0f6f5221ecbd5ae2263f065) | fix | **table:** throw when multiple row templates are used with virtual scrolling ([#32682](https://github.com/angular/components/pull/32682)) | -### material +| [e397ecbcfa](https://github.com/angular/components/commit/e397ecbcfad4e85fde8d116faf5caa80dcddca45) | fix | **table:** remove string-based DI tokens ([#32677](https://github.com/angular/components/pull/32677)) | +### multiple | Commit | Type | Description | | -- | -- | -- | -| [bb69f3ca7d](https://github.com/angular/components/commit/bb69f3ca7daeeaaf30c4c86310c504f9d2925bd5) | fix | **sidenav:** incorrectly trapping focus in some cases ([#32699](https://github.com/angular/components/pull/32699)) | -| [7824225a5b](https://github.com/angular/components/commit/7824225a5b5e9911867e378b8b7529ec852baca2) | fix | **timepicker:** make it easier to style timepicker panel ([#32711](https://github.com/angular/components/pull/32711)) | +| [3612ef6094](https://github.com/angular/components/commit/3612ef6094d268f14e2068db71ff7dada278476c) | fix | incorrect paths in declaration files for aria | +| [f3357c1dff](https://github.com/angular/components/commit/f3357c1dff6e68de75f1c35ff8dde3b4f3874345) | fix | resolve sass if function deprecation warnings ([#32674](https://github.com/angular/components/pull/32674)) | @@ -601,6 +195,11 @@ No user facing changes in this release + +# 21.2.0-next.0 "cadmium-chicken" (2026-01-14) + + + # 21.1.0 "cadmium-crane" (2026-01-14) ### aria diff --git a/MODULE.bazel b/MODULE.bazel index 13db8dd2323a..a9e3fa086352 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,42 +4,42 @@ module( name = "components", ) -bazel_dep(name = "yq.bzl", version = "0.3.6") -bazel_dep(name = "rules_nodejs", version = "6.7.5") -bazel_dep(name = "aspect_rules_js", version = "3.2.3") +bazel_dep(name = "yq.bzl", version = "0.3.4") +bazel_dep(name = "rules_nodejs", version = "6.7.3") +bazel_dep(name = "aspect_rules_js", version = "2.9.2") bazel_dep(name = "rules_pkg", version = "1.2.0") -bazel_dep(name = "tar.bzl", version = "0.10.7") -bazel_dep(name = "bazel_lib", version = "3.5.0") -bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0") -bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") -bazel_dep(name = "platforms", version = "1.1.0") -bazel_dep(name = "aspect_rules_ts", version = "3.9.1") +bazel_dep(name = "tar.bzl", version = "0.8.1") +bazel_dep(name = "aspect_bazel_lib", version = "2.22.5") +bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0") +bazel_dep(name = "aspect_rules_jasmine", version = "2.0.2") +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "aspect_rules_ts", version = "3.8.4") bazel_dep(name = "bazel_skylib", version = "1.9.0") bazel_dep(name = "rules_browsers") git_override( module_name = "rules_browsers", - commit = "3a6bd40f9be945dc51c976a21424c656448f9b09", - remote = "https://github.com/angular/rules_browsers.git", + commit = "ceb52751831f3e6249aba83f688a612e57a8d14a", + remote = "https://github.com/devversion/rules_browsers.git", ) bazel_dep(name = "rules_sass") git_override( module_name = "rules_sass", - commit = "f153cb9242233738c3e6eca6554ae4cb864b0def", - remote = "https://github.com/angular/rules_sass.git", + commit = "1184a80751a21af8348f308abc5b38a41f26850e", + remote = "https://github.com/devversion/rules_sass.git", ) bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "db445dccfe48aad20be6ee3df456df91660ff217", - remote = "https://github.com/angular/rules_angular.git", + commit = "d746c4f75e42cffe389d1ab077f4639be2bc78d1", + remote = "https://github.com/devversion/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "daa0b5ecd15668d12163f70d4c9836ada70808c9", + commit = "0edcf22f9ce677699274f2d399325f99d3d56e81", remote = "https://github.com/angular/dev-infra.git", ) @@ -49,32 +49,32 @@ use_repo(yq, "yq_toolchains") rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext") rules_ts_ext.deps( name = "components_npm_typescript", - # Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.2 | jq -r '.dist.integrity' - ts_integrity = "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", - ts_version = "6.0.2", + # Obtained by: curl --silent https://registry.npmjs.org/typescript/5.9.2 | jq -r '.dist.integrity' + ts_integrity = "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + ts_version = "5.9.2", ) use_repo(rules_ts_ext, **{"npm_typescript": "components_npm_typescript"}) node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") node.toolchain( node_repositories = { - "22.23.1-darwin_arm64": ("node-v22.23.1-darwin-arm64.tar.gz", "node-v22.23.1-darwin-arm64", "ef28d8fab2c0e4314522d4bb1b7173270aa3937e93b92cb7de79c112ac1fa953"), - "22.23.1-darwin_amd64": ("node-v22.23.1-darwin-x64.tar.gz", "node-v22.23.1-darwin-x64", "b8da981b8a0b1241b70249204916da76c63573ddf5814dbd2d1e41069105cb81"), - "22.23.1-linux_arm64": ("node-v22.23.1-linux-arm64.tar.xz", "node-v22.23.1-linux-arm64", "0294e8b915ab75f92c7513d2fcb830ae06e10684e6c603e99a87dbf8835389c1"), - "22.23.1-linux_ppc64le": ("node-v22.23.1-linux-ppc64le.tar.xz", "node-v22.23.1-linux-ppc64le", "a645bcb7bbc498d09ba9249b5c6414aa6d0a461d8854afc61e32096646ba5e66"), - "22.23.1-linux_s390x": ("node-v22.23.1-linux-s390x.tar.xz", "node-v22.23.1-linux-s390x", "f16b5636ba925b462e627c5e0ef47de3255bf4a7b38d56db58e2cf165df19c3c"), - "22.23.1-linux_amd64": ("node-v22.23.1-linux-x64.tar.xz", "node-v22.23.1-linux-x64", "9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578"), - "22.23.1-windows_amd64": ("node-v22.23.1-win-x64.zip", "node-v22.23.1-win-x64", "7df0bc9375723f4a86b3aa1b7cc73342423d9677a8df4538aca31a049e309c29"), + "22.22.0-darwin_arm64": ("node-v22.22.0-darwin-arm64.tar.gz", "node-v22.22.0-darwin-arm64", "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"), + "22.22.0-darwin_amd64": ("node-v22.22.0-darwin-x64.tar.gz", "node-v22.22.0-darwin-x64", "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"), + "22.22.0-linux_arm64": ("node-v22.22.0-linux-arm64.tar.xz", "node-v22.22.0-linux-arm64", "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"), + "22.22.0-linux_ppc64le": ("node-v22.22.0-linux-ppc64le.tar.xz", "node-v22.22.0-linux-ppc64le", "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"), + "22.22.0-linux_s390x": ("node-v22.22.0-linux-s390x.tar.xz", "node-v22.22.0-linux-s390x", "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"), + "22.22.0-linux_amd64": ("node-v22.22.0-linux-x64.tar.xz", "node-v22.22.0-linux-x64", "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"), + "22.22.0-windows_amd64": ("node-v22.22.0-win-x64.zip", "node-v22.22.0-win-x64", "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"), }, - node_version = "22.23.1", + node_version = "22.22.0", ) use_repo(node, "nodejs_toolchains") pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") pnpm.pnpm( name = "pnpm", - pnpm_version = "11.13.1", - pnpm_version_integrity = "sha512-svx2g7imUlQU59E+G6KMqt3elr9m7FQL+ut+cCuB8+C+TR8pXt9/n+A5Z0Co3ORQnFgt33mJH0VD/qMtN2RfJQ==", + pnpm_version = "10.29.3", + pnpm_version_integrity = "sha512-SY4ftMylqgbB3PJhHm+vxQly/+cYmZjECekN50VmREKY/+Q+bNKs3Hdboap8xeCSqLcFTIEbqMV3D4RpPTPS3A==", ) use_repo(pnpm, "pnpm") @@ -108,6 +108,7 @@ npm.npm_translate_lock( "//src/universal-app:package.json", "//src/youtube-player:package.json", ], + npmrc = "//:.npmrc", package_visibility = { "@angular/aria": [ "//integration:__subpackages__", @@ -166,7 +167,6 @@ npm.npm_translate_lock( "@angular/material-date-fns-adapter": [ "//integration:__subpackages__", "//docs:__subpackages__", - "//src/components-examples:__subpackages__", ], "@angular/material-luxon-adapter": [ "//integration:__subpackages__", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 20290de9851d..bd0c8bfc396a 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -18,21 +18,25 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.26.0/MODULE.bazel": "6c902d97038c3ab07b6c4e67c97abc61b20182fcfa84fa7dee82fc724f12e455", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.26.0/source.json": "4cc3ece7ab661bb391a9e24fe55c4b567d60a9ea9d9e91d772dad373cbcb6217", - "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/MODULE.bazel": "fbb819eb8b7e5d7f67fdd38f7cecb413e287594cd666ce192c72c8828527775a", - "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/source.json": "81ffb708333cd98ec3c0b4cc004f4d5cf92a16914b5196a2892c45141bba7cff", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.0/MODULE.bazel": "5fef5ec709c837312823f9bcf0f276661e2cb48ad52f17c4e01176bbf1e9bf58", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.0/source.json": "5e42968c6d23ab8bd95c02634b16864d866334347827cb6a8425b86c11cc4363", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/MODULE.bazel": "45f054400ff242c4433f6d7f20f6123a9a72739cb7a1f44247d738db1644f46c", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/source.json": "3ed399a5654259a822448f9cdbf21f6c738f16ccd7f89249c7507e374cbdd1e3", "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", - "https://bcr.bazel.build/modules/aspect_rules_js/3.0.3/MODULE.bazel": "28a30e8fc33bf64a67835d64d124f6e05a7d59648dcb27b110fb3502f761e503", - "https://bcr.bazel.build/modules/aspect_rules_js/3.2.3/MODULE.bazel": "3a0363a5b8ec4931488dc327c577a78c1f5a725293ebffaa5d2613283701aa8a", - "https://bcr.bazel.build/modules/aspect_rules_js/3.2.3/source.json": "fa3187962f5fdeee0c6226792904aaafb2cf585717a264fa4393a767bffb99fe", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.9.1/MODULE.bazel": "7b2e9f5525c65db1261fa255ff4ed00289fe2aa87fb1014e5ef23c08ecfe2614", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.9.2/MODULE.bazel": "feeb6c45b69c995eca3e5ca5872658c80df658022e01044eca00cf472bb89142", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.9.2/source.json": "cf3075502f798f71a9c5707a7684eaf0b86da11ed440fd90ea34385dc297676a", + "https://bcr.bazel.build/modules/aspect_rules_js/2.4.2/MODULE.bazel": "0d01db38b96d25df7ed952a5e96eac4b3802723d146961974bf020f6dd07591d", + "https://bcr.bazel.build/modules/aspect_rules_js/2.6.2/MODULE.bazel": "ed2a871f4ab8fbde0cab67c425745069d84ea64b64313fa1a2954017326511f5", + "https://bcr.bazel.build/modules/aspect_rules_js/2.9.2/MODULE.bazel": "93fd5b85e6e912fb0712cbab453c43271d4ea33a093f84fd587638fbc9f8c145", + "https://bcr.bazel.build/modules/aspect_rules_js/2.9.2/source.json": "4bff7c03ab387b60deb15649ba575688e62f2a71a7544cbc7a660b19ec473808", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.3/MODULE.bazel": "d09db394970f076176ce7bab5b5fa7f0d560fd4f30b8432ea5e2c2570505b130", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.0/MODULE.bazel": "5aace216caf88638950ef061245d23c36f57c8359e56e97f02a36f70bb09c50f", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.4/MODULE.bazel": "a50254ac3add6232d0f9f93103836f9afaf614315589a13abf74183982c4101d", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.4/source.json": "f786e0763f3ea5de7ea6d4c4e38fccb48bf4d9c5eafaf95091c0e1590502510e", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.6/MODULE.bazel": "cafb8781ad591bc57cc765dca5fefab08cf9f65af363d162b79d49205c7f8af7", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", - "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.4.2/MODULE.bazel": "f31aa84151d31e98cffd43eb7217ccff5ec52bdd5f2d10db8f053aeb23342eca", - "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.4.2/source.json": "d027d264e6b6e7fc421e38189f4374fcd14a67e0bd6e0e705de8c2185c3787e1", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -44,21 +48,19 @@ "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", + "https://bcr.bazel.build/modules/bazel_features/1.39.0/source.json": "f63cbeb4c602098484d57001e5a07d31cb02bbccde9b5e2c9bf0b29d05283e93", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", - "https://bcr.bazel.build/modules/bazel_features/1.41.0/source.json": "8fd525b31b0883c47e0593443cdd10219b94a7556b3195fc02d75c86c66cfe30", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", - "https://bcr.bazel.build/modules/bazel_lib/3.5.0/MODULE.bazel": "1eba0ba67d418d99fb682c36147a9e1725abbc8fc25544be64702e32eda2be29", - "https://bcr.bazel.build/modules/bazel_lib/3.5.0/source.json": "98d49ec7e2b15298c990f09ec8cdea8d4788c2885497aa9cbb0a74e36e994df8", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.0/MODULE.bazel": "2ab127ef8d56a739a99bb2ce00ec4c7d1ecc7977d4370c0ca6efd0d8f03d6d99", "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", @@ -72,23 +74,19 @@ "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", - "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", - "https://bcr.bazel.build/modules/jq.bzl/0.6.1/MODULE.bazel": "f30c46e0a08a9f7566a8bf60a43d48abea960cd7f57b315b01e2762f1537eb52", - "https://bcr.bazel.build/modules/jq.bzl/0.6.1/source.json": "9ca9e2f90baa6a5bb0a49626ed9528554ec83165adf47b39792673ecc7feda22", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", - "https://bcr.bazel.build/modules/package_metadata/0.0.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -97,8 +95,7 @@ "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", - "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", @@ -107,7 +104,6 @@ "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", - "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", @@ -127,7 +123,6 @@ "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc", - "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", @@ -161,10 +156,12 @@ "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", "https://bcr.bazel.build/modules/rules_nodejs/6.5.0/MODULE.bazel": "546d0cf79f36f9f6e080816045f97234b071c205f4542e3351bd4424282a8810", + "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", - "https://bcr.bazel.build/modules/rules_nodejs/6.7.5/MODULE.bazel": "97e6794043821d23c013baa4a50fd1c599f2e6ae92b06e2c5f1cd7074fd83e7c", - "https://bcr.bazel.build/modules/rules_nodejs/6.7.5/source.json": "d60ee5a76258b1c8f99545ed24172b44d43ba64ca1a2dfc04371ef203df19fdf", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", @@ -196,20 +193,17 @@ "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", - "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", - "https://bcr.bazel.build/modules/tar.bzl/0.10.7/MODULE.bazel": "e06d0072c8adef7b9efbeec951d7e6b4b7e6bfa5845c3d8f402289c7b5d6331c", - "https://bcr.bazel.build/modules/tar.bzl/0.10.7/source.json": "c660155f239fcfadfb85f0b9ff304b95390632c15e3f6cb718cd3e08f2bb5c86", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", - "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", + "https://bcr.bazel.build/modules/tar.bzl/0.8.1/MODULE.bazel": "6ffe8907ed4c555bc94bd35a5a01411cc4470c6dace84f9cf487815409e077d1", + "https://bcr.bazel.build/modules/tar.bzl/0.8.1/source.json": "835f83b482facf6205ad8708cf2b2f6524d1d7b1075a90fe9bb540da761d6d2e", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.2.0/MODULE.bazel": "6f3a675677db8885be4d607fde14cc51829715e3a879fb016eb9bf336786ce6d", "https://bcr.bazel.build/modules/yq.bzl/0.3.2/MODULE.bazel": "0384efa70e8033d842ea73aa4b7199fa099709e236a7264345c03937166670b6", "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", - "https://bcr.bazel.build/modules/yq.bzl/0.3.6/MODULE.bazel": "985c2a0cb4ad9994bb0e33cc7fae931c91105eeefe3faa355b8f4c258d0607c0", - "https://bcr.bazel.build/modules/yq.bzl/0.3.6/source.json": "678aaf6e291164f3cd761bb3e872e8a151248f413dbb63c5524a50b82a5bc890", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" @@ -218,8 +212,8 @@ "moduleExtensions": { "@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": { "general": { - "bzlTransitiveDigest": "bNAlf9YOHy6X0mjK8Vfbbysveub9QUXgPlryAYp0umw=", - "usagesDigest": "LSQ+zZp7JNgnBONTxxXnwGr4NTh2qtQYk7qwXXz5qWo=", + "bzlTransitiveDigest": "c4i5gawrp4Au9UMb55EzQCePYwkrFqD9tFBN7GdHG5g=", + "usagesDigest": "ToTaCONCN/E05krnHXLM1kpV1zrHNxHrGpUip973II4=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -274,11 +268,11 @@ "npm__esbuild_0.19.9": { "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", "attributes": { - "key": "npm__esbuild_0.19.9", "package": "esbuild", "version": "0.19.9", "root_package": "", "link_workspace": "", + "link_packages": {}, "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", "url": "", "commit": "", @@ -293,23 +287,20 @@ "npm_auth_password": "", "lifecycle_hooks": [], "extra_build_content": "", - "generate_package_json_bzl": false, "generate_bzl_library_targets": false, "extract_full_archive": false, - "exclude_package_contents": [], - "exclude_package_contents_presets": [] + "exclude_package_contents": [] } }, "npm__esbuild_0.19.9__links": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links_rule", + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", "attributes": { - "key": "npm__esbuild_0.19.9", "package": "esbuild", "version": "0.19.9", + "dev": false, "root_package": "", + "link_packages": {}, "deps": {}, - "deps_oss": {}, - "deps_cpus": {}, "transitive_closure": {}, "lifecycle_build_target": false, "lifecycle_hooks_env": [], @@ -322,12 +313,31 @@ "//visibility:public" ], "replace_package": "", - "exclude_package_contents": [], - "exclude_package_contents_presets": [] + "exclude_package_contents": [] } } }, "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], [ "aspect_rules_esbuild+", "aspect_rules_js", @@ -343,6 +353,11 @@ "bazel_skylib", "bazel_skylib+" ], + [ + "aspect_rules_js+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], [ "aspect_rules_js+", "aspect_rules_js", @@ -355,33 +370,162 @@ ], [ "aspect_rules_js+", - "bazel_features", - "bazel_features+" + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" ], [ "aspect_rules_js+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "tar.bzl+", "bazel_lib", "bazel_lib+" ], [ - "aspect_rules_js+", + "tar.bzl+", "bazel_skylib", "bazel_skylib+" ], [ - "aspect_rules_js+", + "tar.bzl+", + "tar.bzl", + "tar.bzl+" + ] + ] + } + }, + "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { + "general": { + "bzlTransitiveDigest": "HC+l+mTivq1p/KbcVQ+iV5QwYR+oKESJh827FY68SH8=", + "usagesDigest": "CKLxpc3lNVLihz4+HVhcvPgL41EqRS5CZTzZG42uXl0=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pnpm": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", + "attributes": { + "package": "pnpm", + "version": "10.29.3", + "root_package": "", + "link_workspace": "", + "link_packages": {}, + "integrity": "sha512-SY4ftMylqgbB3PJhHm+vxQly/+cYmZjECekN50VmREKY/+Q+bNKs3Hdboap8xeCSqLcFTIEbqMV3D4RpPTPS3A==", + "url": "", + "commit": "", + "patch_args": [ + "-p0" + ], + "patches": [], + "custom_postinstall": "", + "npm_auth": "", + "npm_auth_basic": "", + "npm_auth_username": "", + "npm_auth_password": "", + "lifecycle_hooks": [], + "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", + "generate_bzl_library_targets": false, + "extract_full_archive": true, + "exclude_package_contents": [] + } + }, + "pnpm__links": { + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "attributes": { + "package": "pnpm", + "version": "10.29.3", + "dev": false, + "root_package": "", + "link_packages": {}, + "deps": {}, + "transitive_closure": {}, + "lifecycle_build_target": false, + "lifecycle_hooks_env": [], + "lifecycle_hooks_execution_requirements": [ + "no-sandbox" + ], + "lifecycle_hooks_use_default_shell_env": false, + "bins": {}, + "package_visibility": [ + "//visibility:public" + ], + "replace_package": "", + "exclude_package_contents": [] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_bazel_lib+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_bazel_lib+", "bazel_tools", "bazel_tools" ], + [ + "aspect_bazel_lib+", + "tar.bzl", + "tar.bzl+" + ], [ "aspect_rules_js+", - "protobuf", - "protobuf+" + "aspect_bazel_lib", + "aspect_bazel_lib+" ], [ "aspect_rules_js+", - "tar.bzl", - "tar.bzl+" + "aspect_rules_js", + "aspect_rules_js+" + ], + [ + "aspect_rules_js+", + "aspect_tools_telemetry_report", + "aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report" + ], + [ + "aspect_rules_js+", + "bazel_features", + "bazel_features+" + ], + [ + "aspect_rules_js+", + "bazel_lib", + "bazel_lib+" + ], + [ + "aspect_rules_js+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "aspect_rules_js+", + "bazel_tools", + "bazel_tools" ], [ "bazel_features+", @@ -393,11 +537,6 @@ "bazel_features_version", "bazel_features++version_extension+bazel_features_version" ], - [ - "bazel_lib+", - "bazel_lib", - "bazel_lib+" - ], [ "bazel_lib+", "bazel_skylib", @@ -408,11 +547,6 @@ "bazel_tools", "bazel_tools" ], - [ - "protobuf+", - "proto_bazel_features", - "bazel_features+" - ], [ "tar.bzl+", "bazel_lib", @@ -431,34 +565,93 @@ ] } }, + "@@aspect_rules_ts+//ts:extensions.bzl%ext": { + "general": { + "bzlTransitiveDigest": "QDTi1Wl/eEY4IgbXjRhegUQfHj+bB8ZEVyiSGLZc6qo=", + "usagesDigest": "81NsRsd41daCKu8Q5ap/9nRQmXxXRmDwzKd17wtFeok=", + "recordedFileInputs": { + "@@rules_browsers+//package.json": "772d873d450a539e2133635aeb5e63744cf1cec86e6b37aeecd9267a147fb0d7" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "components_npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.2", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "npm_rules_browsers_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "", + "version_from": "@@rules_browsers+//:package.json", + "integrity": "", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "rules_angular_npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.2", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + }, + "npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "5.9.3", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "aspect_rules_ts+", + "aspect_rules_ts", + "aspect_rules_ts+" + ], + [ + "aspect_rules_ts+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { "general": { - "bzlTransitiveDigest": "4w9RM0xjdKo1crk5zL20a/TuhqO0P1z1LsuXDneBXD4=", - "usagesDigest": "C5lkKv3wg58/Tsv2sAAB812rDJ8WOo8fjfehrnHW0Gc=", + "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", + "usagesDigest": "vdKKVXjay7FaDAQ2dFXDiqaEZRBFoxDm7X0xW0sNT5o=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, - "envVariables": { - "ASPECT_TOOLS_TELEMETRY_TEST": null - }, + "envVariables": {}, "generatedRepoSpecs": { "aspect_tools_telemetry_report": { "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", "attributes": { "deps": { - "aspect_rules_js": "3.2.3", - "aspect_rules_esbuild": "0.26.0", - "aspect_rules_jasmine": "2.0.4", - "aspect_rules_ts": "3.9.2", - "aspect_tools_telemetry": "0.4.2" - }, - "last_notice": 1 + "aspect_rules_js": "2.9.2", + "aspect_rules_esbuild": "0.25.0", + "aspect_rules_jasmine": "2.0.2", + "aspect_rules_ts": "3.8.4", + "aspect_tools_telemetry": "0.3.3" + } } } }, - "moduleExtensionMetadata": { - "useAllRepos": "NO", - "reproducible": false - }, "recordedRepoMappingEntries": [ [ "aspect_tools_telemetry+", @@ -475,7 +668,7 @@ }, "@@pybind11_bazel+//:python_configure.bzl%extension": { "general": { - "bzlTransitiveDigest": "VhEtmxw1yzb9rBZVsKTdti7p+nDM/Fv1p9TmKdO45+Q=", + "bzlTransitiveDigest": "c9ZWWeXeu6bctL4/SsY2otFWyeFN0JJ20+ymGyJZtWk=", "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", "recordedFileInputs": { "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" @@ -509,8 +702,8 @@ }, "@@rules_angular+//setup:extensions.bzl%rules_angular": { "general": { - "bzlTransitiveDigest": "aS7Uud1IzoU7PPLzH3s6IfFS4b2fa0SRWDi2/fS4bQU=", - "usagesDigest": "EgyiBbDdvohGiFNlSH5SHvBZxe9z5rvofPpeWuiSDqs=", + "bzlTransitiveDigest": "fkaH7HMicL3g7/NDaFzlq39kcLopMyQ3KdbDn+5CRzA=", + "usagesDigest": "rqqEVjxQ96set3CHBp1fQWOJEOXL24im9NxxILDpOq0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -518,15 +711,22 @@ "components_rules_angular_configurable_deps": { "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", "attributes": { - "angular_compiler_cli": "@@//:node_modules/@angular/compiler-cli", - "typescript": "@@//:node_modules/typescript" + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript" } }, "rules_angular_configurable_deps": { "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", "attributes": { - "angular_compiler_cli": "@@//:node_modules/@angular/compiler-cli", - "typescript": "@@//:node_modules/typescript" + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript-local" + } + }, + "dev_infra_rules_angular_configurable_deps": { + "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", + "attributes": { + "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", + "typescript": "@@rules_angular+//:node_modules/typescript" } } }, @@ -535,7 +735,7 @@ }, "@@rules_browsers+//browsers:extensions.bzl%browsers": { "general": { - "bzlTransitiveDigest": "3W9m9j39szqkOoSVVqamCaSwCCw7x5Wq1kcYTwYBNOc=", + "bzlTransitiveDigest": "Bm6fiKpWy96aLohOlLCP36ARVxRLZm/R+smhsb2HzmI=", "usagesDigest": "FmXYJVoVJlnfUU8x8gObSvu4qWcco/9Faw61aC/wBF0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -544,9 +744,9 @@ "rules_browsers_chrome_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "13d6638183ccab406ee16e28730826bd3ea57fe564a741bc935401221dfec64f", + "sha256": "1ac33f89306327af43be159c03ca4a26486de0858f42fe52394acdef50364143", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/linux64/chrome-headless-shell-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/linux64/chrome-headless-shell-linux64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell" @@ -562,9 +762,9 @@ "rules_browsers_chrome_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "b50ee6ddb3bf30c91f3094fa41cc8e8de60e317c63ef058db2375346cde78d98", + "sha256": "169ff49c465cfda52931395e61861e146dfc5013e92c01ca792db5acea858d0b", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/mac-x64/chrome-headless-shell-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-x64/chrome-headless-shell-mac-x64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell" @@ -580,9 +780,9 @@ "rules_browsers_chrome_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "49859c1d3b0d29a82c58765e9e5fe21efa0bf7c03a86fc91eb5f3f94f34c45b6", + "sha256": "aeaaaaa4d68193a21bed04c44ddeb1230232707b4ea1d845a92925787509cd8e", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell" @@ -598,9 +798,9 @@ "rules_browsers_chrome_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "d161d88a999effd212ce87df2ff710be1a25a495cc428d4c3029feccd02dafe9", + "sha256": "4d6d79bcbcb22084df6e3a3d3a2caff67d6c0fa488d63f0c7ec1526f9553db8c", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/win64/chrome-headless-shell-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/win64/chrome-headless-shell-win64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe" @@ -616,9 +816,9 @@ "rules_browsers_chromedriver_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "7bb2eedf7030523ba617704bf6c3e472b5974d048d7fa6ac6a764904556c6f36", + "sha256": "0607ccf6810a07ae08cac6443beac8b23f88dd53c7f1e0299e22d65f7cd2d020", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/linux64/chromedriver-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/linux64/chromedriver-linux64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-linux64/chromedriver" @@ -632,9 +832,9 @@ "rules_browsers_chromedriver_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "5c1fffef90ecea2bb0c12c5a9c0549116fc7acda13cce15881da4aa8b74e6794", + "sha256": "0f512a9dd683ed4c41e609d8d02c07807497dbad3ab2f95f0d583486be7b8cff", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/mac-x64/chromedriver-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-x64/chromedriver-mac-x64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-x64/chromedriver" @@ -648,9 +848,9 @@ "rules_browsers_chromedriver_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "a33d54c99a50fef25cbedb7c3d3cdd838dd784556138648bcc5da5814109a306", + "sha256": "7d6fc6d17de1733eb6739d1ea16d085c8df1568bcf9fa0d130c2784b27f38268", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/mac-arm64/chromedriver-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-arm64/chromedriver-mac-arm64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver" @@ -664,9 +864,9 @@ "rules_browsers_chromedriver_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "2673d6a87dabd50c516d4d69b4daf742ff6169877c04bb226971503012262d8c", + "sha256": "f4e9fb7bbf692fde7979b24e8d737b3cef4baafbc7a370e5d0abc4a8450fd830", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/152.0.7960.0/win64/chromedriver-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/win64/chromedriver-win64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-win64/chromedriver.exe" @@ -680,9 +880,9 @@ "rules_browsers_firefox_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "748704b06ffaabc8bd2b3351c93c244db4e29bfe21af6ecb4d6c124c7bd4a234", + "sha256": "f055b9c0d7346a10d22edc7f10e08679af2ea495367381ab2be9cab3ec6add97", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/152.0/linux-x86_64/en-US/firefox-152.0.tar.xz" + "https://archive.mozilla.org/pub/firefox/releases/147.0/linux-x86_64/en-US/firefox-147.0.tar.xz" ], "named_files": { "FIREFOX": "firefox/firefox" @@ -696,9 +896,9 @@ "rules_browsers_firefox_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "b8a46188850d2fb32f16ad3c0829b08cd689bc714b8ee18fd9b09bb60629004d", + "sha256": "48485e2068bc726e2f30cf5855fc2da1fc75c1272bc243a5394f428ffae3ba35", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/152.0/mac/en-US/Firefox%20152.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/147.0/mac/en-US/Firefox%20147.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -712,9 +912,9 @@ "rules_browsers_firefox_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "b8a46188850d2fb32f16ad3c0829b08cd689bc714b8ee18fd9b09bb60629004d", + "sha256": "48485e2068bc726e2f30cf5855fc2da1fc75c1272bc243a5394f428ffae3ba35", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/152.0/mac/en-US/Firefox%20152.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/147.0/mac/en-US/Firefox%20147.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -728,9 +928,9 @@ "rules_browsers_firefox_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "3c426623ff83cb014402607c7ec99fea5b39eec2e44a07f639680c393ff9784e", + "sha256": "36ff9e150875aa48a0af9eec3eb67f66dddd8efac5c743265371a72ae3e796c4", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/152.0/win64/en-US/Firefox%20Setup%20152.0.exe" + "https://archive.mozilla.org/pub/firefox/releases/147.0/win64/en-US/Firefox%20Setup%20147.0.exe" ], "named_files": { "FIREFOX": "core/firefox.exe" @@ -747,7 +947,7 @@ }, "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { - "bzlTransitiveDigest": "CYUiFDCnL2VGx3uotIu/VuGabgObnZra2zzRUJCX0sU=", + "bzlTransitiveDigest": "WHRlQQnxW7e7XMRBhq7SARkDarLDOAbg6iLaJpk5QYM=", "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -830,7 +1030,7 @@ }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", + "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -892,10 +1092,432 @@ ] } }, + "@@rules_nodejs+//nodejs:extensions.bzl%node": { + "general": { + "bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=", + "usagesDigest": "MxScWq8otPWPlxpMajQ2uFZlan+7uzSTDuEV/yFC9No=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "22.22.0-darwin_arm64": [ + "node-v22.22.0-darwin-arm64.tar.gz", + "node-v22.22.0-darwin-arm64", + "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + ], + "22.22.0-darwin_amd64": [ + "node-v22.22.0-darwin-x64.tar.gz", + "node-v22.22.0-darwin-x64", + "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + ], + "22.22.0-linux_arm64": [ + "node-v22.22.0-linux-arm64.tar.xz", + "node-v22.22.0-linux-arm64", + "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + ], + "22.22.0-linux_ppc64le": [ + "node-v22.22.0-linux-ppc64le.tar.xz", + "node-v22.22.0-linux-ppc64le", + "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + ], + "22.22.0-linux_s390x": [ + "node-v22.22.0-linux-s390x.tar.xz", + "node-v22.22.0-linux-s390x", + "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + ], + "22.22.0-linux_amd64": [ + "node-v22.22.0-linux-x64.tar.xz", + "node-v22.22.0-linux-x64", + "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + ], + "22.22.0-windows_amd64": [ + "node-v22.22.0-win-x64.zip", + "node-v22.22.0-win-x64", + "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "22.22.0", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" + } + } + }, + "recordedRepoMappingEntries": [] + } + }, "@@rules_python+//python/extensions:pip.bzl%pip": { "general": { - "bzlTransitiveDigest": "weEvgIUjy7e5WRM5OvjzsfGuVlHNMpaVlfYXco8WUIk=", - "usagesDigest": "AK1R124YPWwAs8z1CQYyjYuci8RO5Ofot+EP5ZCNQDc=", + "bzlTransitiveDigest": "d3ENjFH8qMwmOrkcb3c9JYqQ5hJ6owjfbSr24KY0Ugg=", + "usagesDigest": "/9NP3RV6/DWuNdYAsIU/8UCgCX0TdPUJr0X6O+0lrtk=", "recordedFileInputs": { "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", "@@rules_fuzzing+//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", @@ -3632,7 +4254,7 @@ }, "@@rules_sass+//src/toolchain:extensions.bzl%sass": { "general": { - "bzlTransitiveDigest": "mOfuR8PsNuUWEq7JZ4MpIRbwyAGAqrCvkXXGaRNnlPQ=", + "bzlTransitiveDigest": "RA58Nyrsn03Z5YmQnpmBw3mqlVck++XIrx34amsqU/E=", "usagesDigest": "R0KshhzIouLWuexMUCrl4HY+FUDwlVVgF9Z7UnwyUWA=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -3688,8 +4310,8 @@ }, "@@yq.bzl+//yq:extensions.bzl%yq": { "general": { - "bzlTransitiveDigest": "UfFMy8CWK4/dVo/tfaSAIYUiDGNAPes5eRllx9O9Q9Q=", - "usagesDigest": "d95JdxMoSR8TPZLqYZj/FW+uwHJniOTtIal4UqqFH6Y=", + "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", + "usagesDigest": "jINqtIprI8xAo9NmAr+0vIXtkTtn9IasKnGcw8TX32U=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -3768,9 +4390,5 @@ } } }, - "facts": { - "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { - "notice_version": "1" - } - } + "facts": {} } diff --git a/README.md b/README.md index 5d46e71d94df..239f35346b48 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,6 @@ What do we mean by "high-quality" components? * Performance cost is minimized. * Code is clean and well-documented to serve as an example for Angular developers. -## Support policy -Angular Material and the CDK follow the same support and release policy as the Angular framework. -For details on supported versions and update practices, see the -[Angular support policy and schedule](https://angular.dev/reference/releases). - ## Browser and screen reader support The Angular Components team supports the most recent two versions of all major browsers: Chrome (including Android), Firefox, Safari (including iOS), and Edge. diff --git a/docs/.npmrc b/docs/.npmrc new file mode 100644 index 000000000000..319e41e69dc9 --- /dev/null +++ b/docs/.npmrc @@ -0,0 +1 @@ +strict-peer-dependencies=false diff --git a/docs/.nvmrc b/docs/.nvmrc index f9e7451e7715..85e502778f62 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -22.23.1 +22.22.0 diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 08aa4f888c01..ef8437d87ec7 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -1,5 +1,5 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library", "js_test") -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@npm//:defs.bzl", "npm_link_all_packages") load("@rules_angular//src/architect:ng_config.bzl", "ng_config") load("//docs:defs.bzl", "ng_app") diff --git a/docs/defs.bzl b/docs/defs.bzl index 81adbbf96435..dcca8899fa41 100644 --- a/docs/defs.bzl +++ b/docs/defs.bzl @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@rules_angular//src/architect:ng_application.bzl", "ng_application") load("@rules_angular//src/architect:ng_test.bzl", "ng_test") diff --git a/docs/package.json b/docs/package.json index 15749393f808..7ced509a11af 100644 --- a/docs/package.json +++ b/docs/package.json @@ -61,7 +61,7 @@ "@types/jasmine": "^6.0.0", "@types/node": "^22.14.1", "@types/shelljs": "0.10.0", - "firebase-tools": "15.23.0", + "firebase-tools": "15.2.1", "jasmine-core": "^6.0.0", "jasmine-spec-reporter": "7.0.0", "karma": "~6.4.4", @@ -74,10 +74,10 @@ "lighthouse-logger": "~2.0.0", "npm-run-all": "^4.1.5", "protractor": "^7.0.0", - "puppeteer-core": "^25.0.0", - "sass": "1.101.0", + "puppeteer-core": "^24.6.1", + "sass": "1.97.2", "shelljs": "^0.10.0", "ts-node": "10.9.2", - "typescript": "6.0.2" + "typescript": "~5.9.2" } } diff --git a/docs/scenes/src/app/scene-overlay-container.ts b/docs/scenes/src/app/scene-overlay-container.ts index ab111e8faa55..ec3986695ac7 100644 --- a/docs/scenes/src/app/scene-overlay-container.ts +++ b/docs/scenes/src/app/scene-overlay-container.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {OverlayContainer} from '@angular/cdk/overlay'; -@Service() +@Injectable({providedIn: 'root'}) export class SceneOverlayContainer extends OverlayContainer { _createContainer(): void { const container = this._document.createElement('div'); diff --git a/docs/src/app/material-docs-app.ts b/docs/src/app/material-docs-app.ts index 0bfdcf25ecf9..8b8f4d55fe08 100644 --- a/docs/src/app/material-docs-app.ts +++ b/docs/src/app/material-docs-app.ts @@ -6,13 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - OnDestroy, - ViewEncapsulation, - inject, -} from '@angular/core'; +import {Component, OnDestroy, ViewEncapsulation, inject} from '@angular/core'; import {AnalyticsService} from './shared/analytics/analytics'; import {NavigationFocusService} from './shared/navigation-focus/navigation-focus.service'; @@ -33,7 +27,6 @@ import {HeaderTagManager} from './shared/header-tag-manager'; styleUrls: ['./material-docs-app.scss'], encapsulation: ViewEncapsulation.None, imports: [NavBar, RouterOutlet, CookiePopup], - changeDetection: ChangeDetectionStrategy.Eager, }) export class MaterialDocsApp implements OnDestroy { private _subscriptions = new Subscription(); diff --git a/docs/src/app/pages/component-category-list/component-category-list.ts b/docs/src/app/pages/component-category-list/component-category-list.ts index eb383ac98f02..7501dc3ab3ab 100644 --- a/docs/src/app/pages/component-category-list/component-category-list.ts +++ b/docs/src/app/pages/component-category-list/component-category-list.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, OnDestroy, OnInit, inject} from '@angular/core'; +import {Component, OnDestroy, OnInit, inject} from '@angular/core'; import {ActivatedRoute, RouterLink} from '@angular/router'; import {MatRipple} from '@angular/material/core'; import {NgTemplateOutlet} from '@angular/common'; @@ -26,7 +26,6 @@ import {ComponentPageTitle} from '../page-title/page-title'; templateUrl: './component-category-list.html', styleUrls: ['./component-category-list.scss'], imports: [NavigationFocus, RouterLink, MatRipple, NgTemplateOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) export class ComponentCategoryList implements OnInit, OnDestroy { private readonly _docItems = inject(DocumentationItems); diff --git a/docs/src/app/pages/component-sidenav/component-sidenav-can-load-guard.ts b/docs/src/app/pages/component-sidenav/component-sidenav-can-load-guard.ts index 32796ca2106e..b369b8c251e2 100644 --- a/docs/src/app/pages/component-sidenav/component-sidenav-can-load-guard.ts +++ b/docs/src/app/pages/component-sidenav/component-sidenav-can-load-guard.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; import {ActivatedRouteSnapshot, Router} from '@angular/router'; import {SECTIONS} from '../../shared/documentation-items/documentation-items'; @@ -14,7 +14,7 @@ import {SECTIONS} from '../../shared/documentation-items/documentation-items'; * Guard to determine if the sidenav can load, based on whether the section exists in documentation * items. */ -@Service() +@Injectable({providedIn: 'root'}) export class CanActivateComponentSidenav { private _router = inject(Router); diff --git a/docs/src/app/pages/component-sidenav/component-sidenav.ts b/docs/src/app/pages/component-sidenav/component-sidenav.ts index 6b15d2bb44ba..38064a23b43a 100644 --- a/docs/src/app/pages/component-sidenav/component-sidenav.ts +++ b/docs/src/app/pages/component-sidenav/component-sidenav.ts @@ -6,14 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - ViewEncapsulation, - forwardRef, - inject, - viewChild, -} from '@angular/core'; +import {Component, ViewEncapsulation, forwardRef, inject, viewChild} from '@angular/core'; import {BreakpointObserver} from '@angular/cdk/layout'; import {AsyncPipe} from '@angular/common'; import {MatListItem, MatNavList} from '@angular/material/list'; @@ -53,7 +46,6 @@ const SMALL_WIDTH_BREAKPOINT = 959; templateUrl: './component-sidenav.html', styleUrls: ['./component-sidenav.scss'], encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatSidenav, MatSidenavContainer, @@ -105,7 +97,6 @@ export class ComponentSidenav { selector: 'app-component-nav', templateUrl: './component-nav.html', imports: [MatNavList, MatListItem, RouterLinkActive, RouterLink, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) export class ComponentNav { private _docItems = inject(DocumentationItems); diff --git a/docs/src/app/pages/component-viewer/component-styling.ts b/docs/src/app/pages/component-viewer/component-styling.ts index f9231081d818..6fa162f2e1c4 100644 --- a/docs/src/app/pages/component-viewer/component-styling.ts +++ b/docs/src/app/pages/component-viewer/component-styling.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, Service} from '@angular/core'; +import {Component, inject, Injectable} from '@angular/core'; import {DomSanitizer} from '@angular/platform-browser'; import {HttpClient} from '@angular/common/http'; import {AsyncPipe} from '@angular/common'; @@ -25,7 +25,7 @@ interface StyleOverridesData { }[]; } -@Service() +@Injectable({providedIn: 'root'}) class TokenService { private _http = inject(HttpClient); @@ -52,7 +52,6 @@ class TokenService { selector: 'component-styling', templateUrl: './component-styling.html', imports: [AsyncPipe, TokenTable], - changeDetection: ChangeDetectionStrategy.Eager, }) export class ComponentStyling { private _componentViewer = inject(ComponentViewer); diff --git a/docs/src/app/pages/component-viewer/token-table.html b/docs/src/app/pages/component-viewer/token-table.html index 10a2aa9c477b..81ca2c59a147 100644 --- a/docs/src/app/pages/component-viewer/token-table.html +++ b/docs/src/app/pages/component-viewer/token-table.html @@ -24,9 +24,9 @@ - Filter by default value - + Filter by system token + @@ -38,7 +38,7 @@ Name Type - Default value + Based on system token @@ -47,7 +47,13 @@ {{token.type | titlecase}} - {{token.value}} + + @if (token.derivedFrom) { + + } @else { + None + } + } @empty { diff --git a/docs/src/app/pages/component-viewer/token-table.scss b/docs/src/app/pages/component-viewer/token-table.scss index 24287a8290e1..def638744498 100644 --- a/docs/src/app/pages/component-viewer/token-table.scss +++ b/docs/src/app/pages/component-viewer/token-table.scss @@ -32,6 +32,6 @@ thead { width: 10%; } -.docs-value-header { +.docs-system-header { width: 30%; } diff --git a/docs/src/app/pages/component-viewer/token-table.ts b/docs/src/app/pages/component-viewer/token-table.ts index 7b44fe4920bb..80b2f942cb3d 100644 --- a/docs/src/app/pages/component-viewer/token-table.ts +++ b/docs/src/app/pages/component-viewer/token-table.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, computed, input, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, computed, input, signal} from '@angular/core'; import {TitleCasePipe} from '@angular/common'; import {MatButton, MatIconButton} from '@angular/material/button'; import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field'; @@ -24,13 +24,13 @@ export interface Token { prefix: string; type: TokenType; derivedFrom?: string; - value: string | number | null; } @Component({ selector: 'token-table', templateUrl: './token-table.html', styleUrl: './token-table.scss', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatButton, MatIconButton, @@ -51,24 +51,24 @@ export class TokenTable { protected readonly nameFilter = signal(''); protected readonly typeFilter = signal(null); - protected readonly defaultValueFilter = signal(''); + protected readonly systemTokenFilter = signal(''); protected readonly types: TokenType[] = ['base', 'color', 'typography', 'density']; protected readonly filteredTokens = computed(() => { const name = this.nameFilter().trim().toLowerCase(); const typeFilter = this.typeFilter(); - const defaultValueFilter = this.defaultValueFilter(); + const systemTokenFilter = this.systemTokenFilter(); return this.tokens().filter( token => (!name || token.overridesName.toLowerCase().includes(name)) && (!typeFilter || token.type === typeFilter) && - (!defaultValueFilter || token.value?.toString().toLowerCase().includes(defaultValueFilter)), + (!systemTokenFilter || token.derivedFrom?.toLowerCase().includes(systemTokenFilter)), ); }); protected reset() { this.nameFilter.set(''); this.typeFilter.set(null); - this.defaultValueFilter.set(''); + this.systemTokenFilter.set(''); } } diff --git a/docs/src/app/pages/guide-list/guide-list.ts b/docs/src/app/pages/guide-list/guide-list.ts index dcfd7e2ecc05..daf7e770cf65 100644 --- a/docs/src/app/pages/guide-list/guide-list.ts +++ b/docs/src/app/pages/guide-list/guide-list.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, OnInit, inject} from '@angular/core'; +import {Component, OnInit, inject} from '@angular/core'; import {GuideItems} from '../../shared/guide-items/guide-items'; import {RouterLink} from '@angular/router'; import {Footer} from '../../shared/footer/footer'; @@ -20,7 +20,6 @@ import {MatRipple} from '@angular/material/core'; templateUrl: './guide-list.html', styleUrls: ['./guide-list.scss'], imports: [NavigationFocus, RouterLink, MatCard, MatCardTitle, MatCardContent, Footer, MatRipple], - changeDetection: ChangeDetectionStrategy.Eager, host: { 'class': 'docs-main-content', }, diff --git a/docs/src/app/pages/guide-viewer/guide-viewer.ts b/docs/src/app/pages/guide-viewer/guide-viewer.ts index 7b6391289055..118383c46f32 100644 --- a/docs/src/app/pages/guide-viewer/guide-viewer.ts +++ b/docs/src/app/pages/guide-viewer/guide-viewer.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core'; +import {Component, OnInit, inject, signal} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {GuideItem, GuideItems} from '../../shared/guide-items/guide-items'; import {Footer} from '../../shared/footer/footer'; @@ -21,7 +21,6 @@ import {DocViewer} from '../../shared/doc-viewer/doc-viewer'; templateUrl: './guide-viewer.html', styleUrls: ['./guide-viewer.scss'], imports: [DocViewer, NavigationFocus, TableOfContents, Footer], - changeDetection: ChangeDetectionStrategy.Eager, host: { 'class': 'docs-main-content', }, diff --git a/docs/src/app/pages/page-title/page-title.ts b/docs/src/app/pages/page-title/page-title.ts index 20de73c9dee5..a86fb29f0f52 100644 --- a/docs/src/app/pages/page-title/page-title.ts +++ b/docs/src/app/pages/page-title/page-title.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; import {Title} from '@angular/platform-browser'; export const TITLE_SEPARATOR = ' • '; @@ -14,7 +14,7 @@ export const TITLE_SEPARATOR = ' • '; /** * Service responsible for setting the title that appears above the components and guide pages. */ -@Service() +@Injectable({providedIn: 'root'}) export class ComponentPageTitle { private _bodyTitle = inject(Title); diff --git a/docs/src/app/shared/analytics/analytics.ts b/docs/src/app/shared/analytics/analytics.ts index 64c8689e760e..7da39e34e77e 100644 --- a/docs/src/app/shared/analytics/analytics.ts +++ b/docs/src/app/shared/analytics/analytics.ts @@ -6,11 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {environment} from '../../../environments/environment'; import {formatErrorEventForAnalytics} from './format-error'; +/** Extension of `Window` with potential Google Analytics fields. */ +declare global { + interface Window { + dataLayer?: any[]; + gtag?(...args: any[]): void; + /** Legacy Universal Analytics `analytics.js` field. */ + ga?(...args: any[]): void; + } +} + /** * Google Analytics Service - captures app behaviors and sends them to Google Analytics. * @@ -21,16 +31,9 @@ import {formatErrorEventForAnalytics} from './format-error'; * - Data is uploaded to a legacy Universal Analytics property. * - Data is uploaded to our main Google Analytics 4+ property. */ -@Service() +@Injectable({providedIn: 'root'}) export class AnalyticsService { private _previousUrl: string | undefined; - private _gaWindow = window as Window & - typeof globalThis & { - dataLayer?: any[]; - gtag?(...args: any[]): void; - /** Legacy Universal Analytics `analytics.js` field. */ - ga?(...args: any[]): void; - }; constructor() { this._installGlobalSiteTag(); @@ -68,31 +71,34 @@ export class AnalyticsService { } private _legacyGa(...args: any[]) { - this._gaWindow.ga?.(...args); + if (window.ga) { + window.ga(...args); + } } private _gtag(...args: any[]) { - this._gaWindow.gtag?.(...args); + if (window.gtag) { + window.gtag(...args); + } } private _installGlobalSiteTag() { - const gaWindow = this._gaWindow; const url = `https://www.googletagmanager.com/gtag/js?id=${environment.googleAnalyticsMaterialId}`; // Note: This cannot be an arrow function as `gtag.js` expects an actual `Arguments` // instance with e.g. `callee` to be set. Do not attempt to change this and keep this // as much as possible in sync with the tracking code snippet suggested by the Google // Analytics 4 web UI under `Data Streams`. - gaWindow.dataLayer = gaWindow.dataLayer || []; - gaWindow.gtag = function () { - gaWindow.dataLayer?.push(arguments); + window.dataLayer = window.dataLayer || []; + window.gtag = function () { + window.dataLayer?.push(arguments); }; - gaWindow.gtag('js', new Date()); + window.gtag('js', new Date()); // Configure properties before loading the script. This is necessary to avoid // loading multiple instances of the gtag JS scripts. - gaWindow.gtag('config', environment.googleAnalyticsOverallDomainId); - gaWindow.gtag('config', environment.googleAnalyticsMaterialId); + window.gtag('config', environment.googleAnalyticsOverallDomainId); + window.gtag('config', environment.googleAnalyticsMaterialId); // skip `gtag` for Protractor e2e tests. if (window.name.includes('NG_DEFER_BOOTSTRAP')) { diff --git a/docs/src/app/shared/analytics/error-report-handler.ts b/docs/src/app/shared/analytics/error-report-handler.ts index 846ca695524b..ecbeffade8fa 100644 --- a/docs/src/app/shared/analytics/error-report-handler.ts +++ b/docs/src/app/shared/analytics/error-report-handler.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ErrorHandler, Service, inject} from '@angular/core'; +import {ErrorHandler, Injectable, inject} from '@angular/core'; import {AnalyticsService} from './analytics'; import {formatErrorForAnalytics} from './format-error'; -@Service({autoProvided: false}) +@Injectable() export class AnalyticsErrorReportHandler extends ErrorHandler { private _analytics = inject(AnalyticsService); diff --git a/docs/src/app/shared/carousel/carousel.spec.ts b/docs/src/app/shared/carousel/carousel.spec.ts index 1c072e4f2d45..098b0bc60d12 100644 --- a/docs/src/app/shared/carousel/carousel.spec.ts +++ b/docs/src/app/shared/carousel/carousel.spec.ts @@ -1,9 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - provideZoneChangeDetection, - ViewChild, -} from '@angular/core'; +import {Component, provideZoneChangeDetection, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; import {provideRouter} from '@angular/router'; import {Carousel, CarouselItem} from './carousel'; @@ -66,7 +61,6 @@ describe('HorizontalCarousel', () => { `, ], imports: [Carousel, CarouselItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class CarouselTestComponent { @ViewChild(Carousel) carousel!: Carousel; diff --git a/docs/src/app/shared/carousel/carousel.ts b/docs/src/app/shared/carousel/carousel.ts index 9c372e444054..e8a40152afdb 100644 --- a/docs/src/app/shared/carousel/carousel.ts +++ b/docs/src/app/shared/carousel/carousel.ts @@ -16,7 +16,6 @@ import { contentChildren, viewChild, inject, - ChangeDetectionStrategy, } from '@angular/core'; import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y'; import {LEFT_ARROW, RIGHT_ARROW, TAB} from '@angular/cdk/keycodes'; @@ -46,7 +45,6 @@ export class CarouselItem implements FocusableOption { styleUrls: ['./carousel.scss'], encapsulation: ViewEncapsulation.None, imports: [MatIconButton, MatIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) export class Carousel implements AfterContentInit { readonly ariaLabel = input(undefined, {alias: 'aria-label'}); diff --git a/docs/src/app/shared/cookie-popup/cookie-popup.ts b/docs/src/app/shared/cookie-popup/cookie-popup.ts index 656259f534b3..124fb9fd0a79 100644 --- a/docs/src/app/shared/cookie-popup/cookie-popup.ts +++ b/docs/src/app/shared/cookie-popup/cookie-popup.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatAnchor, MatButton} from '@angular/material/button'; const STORAGE_KEY = 'docs-cookies'; @@ -15,6 +15,7 @@ const STORAGE_KEY = 'docs-cookies'; selector: 'app-cookie-popup', templateUrl: './cookie-popup.html', styleUrls: ['./cookie-popup.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatButton, MatAnchor], }) export class CookiePopup { diff --git a/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts index 5ee8eed857e0..4a23a0288986 100644 --- a/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts +++ b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; +import {Component, Input} from '@angular/core'; import {MatIcon} from '@angular/material/icon'; /** @@ -28,7 +28,6 @@ const ANGULAR_ARIA_LINKS: Record = { templateUrl: 'angular-aria-banner.html', styleUrl: 'angular-aria-banner.css', imports: [MatIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) export class AngularAriaBanner { @Input() componentName: string = ''; diff --git a/docs/src/app/shared/doc-viewer/deprecated-tooltip.ts b/docs/src/app/shared/doc-viewer/deprecated-tooltip.ts index 6dd2e3810080..c2e87111bb0f 100644 --- a/docs/src/app/shared/doc-viewer/deprecated-tooltip.ts +++ b/docs/src/app/shared/doc-viewer/deprecated-tooltip.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; import {MatTooltip} from '@angular/material/tooltip'; /** Shows the deprecation message for a specific field as a tooltip. */ @@ -14,7 +14,6 @@ import {MatTooltip} from '@angular/material/tooltip'; selector: 'deprecated-field', template: `
`, imports: [MatTooltip], - changeDetection: ChangeDetectionStrategy.Eager, }) export class DeprecatedFieldComponent { /** Message regarding the deprecation */ diff --git a/docs/src/app/shared/doc-viewer/doc-viewer.spec.ts b/docs/src/app/shared/doc-viewer/doc-viewer.spec.ts index 807ea9bf1f2d..67f02dab336e 100644 --- a/docs/src/app/shared/doc-viewer/doc-viewer.spec.ts +++ b/docs/src/app/shared/doc-viewer/doc-viewer.spec.ts @@ -1,6 +1,6 @@ import {Clipboard} from '@angular/cdk/clipboard'; import {HttpTestingController, provideHttpClientTesting} from '@angular/common/http/testing'; -import {ChangeDetectionStrategy, Component, provideZoneChangeDetection} from '@angular/core'; +import {Component, provideZoneChangeDetection} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {MatIconButton} from '@angular/material/button'; import {MatTooltip} from '@angular/material/tooltip'; @@ -216,7 +216,6 @@ describe('DocViewer', () => { selector: 'test', template: ``, imports: [DocViewer], - changeDetection: ChangeDetectionStrategy.Eager, }) class DocViewerTestComponent { documentUrl = 'http://material.angular.dev/simple-doc.html'; @@ -272,7 +271,6 @@ class TestComponent {} selector: 'test', template: ``, imports: [DocViewer, TestComponent], - changeDetection: ChangeDetectionStrategy.Eager, }) class DocViewerWithCompTestComponent { component = TestComponent; diff --git a/docs/src/app/shared/doc-viewer/doc-viewer.ts b/docs/src/app/shared/doc-viewer/doc-viewer.ts index 9d6da8303cd1..2fcc06ce5c29 100644 --- a/docs/src/app/shared/doc-viewer/doc-viewer.ts +++ b/docs/src/app/shared/doc-viewer/doc-viewer.ts @@ -20,7 +20,7 @@ import { Component, ElementRef, EventEmitter, - Service, + Injectable, Injector, Input, NgZone, @@ -31,7 +31,6 @@ import { input, inject, Type, - ChangeDetectionStrategy, } from '@angular/core'; import {Observable, Subscription} from 'rxjs'; import {shareReplay, take, tap} from 'rxjs/operators'; @@ -41,7 +40,7 @@ import {DeprecatedFieldComponent} from './deprecated-tooltip'; import {ModuleImportCopyButton} from './module-import-copy-button'; import {AngularAriaBanner} from './angular-aria-banner/angular-aria-banner'; -@Service() +@Injectable({providedIn: 'root'}) class DocFetcher { private _http = inject(HttpClient); @@ -67,7 +66,6 @@ class DocFetcher { } `, imports: [CdkPortalOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) export class DocViewer implements OnDestroy { private _appRef = inject(ApplicationRef); diff --git a/docs/src/app/shared/documentation-items/documentation-items.ts b/docs/src/app/shared/documentation-items/documentation-items.ts index abab9a455838..26cb3d2b7dee 100644 --- a/docs/src/app/shared/documentation-items/documentation-items.ts +++ b/docs/src/app/shared/documentation-items/documentation-items.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import type {LiveExample} from '@angular/components-examples'; export interface AdditionalApiDoc { @@ -599,7 +599,7 @@ interface DocsData { examples: Record; } -@Service() +@Injectable({providedIn: 'root'}) export class DocumentationItems { private _cachedData: DocsData | null = null; @@ -624,26 +624,21 @@ export class DocumentationItems { if (!this._cachedData) { const examples = (await import('@angular/components-examples')).EXAMPLE_COMPONENTS; const exampleNames = Object.keys(examples); - const components = this._processDocs('material', exampleNames, DOCS[COMPONENTS], examples); - const cdk = this._processDocs('cdk', exampleNames, DOCS[CDK], examples); + const components = this._processDocs('material', exampleNames, DOCS[COMPONENTS]); + const cdk = this._processDocs('cdk', exampleNames, DOCS[CDK]); + this._cachedData = {components, cdk, all: [...components, ...cdk], examples}; } return this._cachedData; } - private _processDocs( - packageName: string, - exampleNames: string[], - docs: DocItem[], - examples: Record, - ): DocItem[] { + private _processDocs(packageName: string, exampleNames: string[], docs: DocItem[]): DocItem[] { for (const doc of docs) { doc.packageName = packageName; doc.hasStyling ??= packageName === 'material'; doc.examples = exampleNames.filter( key => - examples[key].packagePath.startsWith(packageName) && key.match(RegExp(`^${doc.exampleSpecs.prefix}`)) && !doc.exampleSpecs.exclude?.some(excludeName => key.indexOf(excludeName) === 0), ); diff --git a/docs/src/app/shared/example-viewer/code-snippet.ts b/docs/src/app/shared/example-viewer/code-snippet.ts index bbfffe42f517..01281e512194 100644 --- a/docs/src/app/shared/example-viewer/code-snippet.ts +++ b/docs/src/app/shared/example-viewer/code-snippet.ts @@ -6,13 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, forwardRef, input, viewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, forwardRef, input, viewChild} from '@angular/core'; import {DocViewer} from '../doc-viewer/doc-viewer'; @Component({ selector: 'code-snippet', templateUrl: './code-snippet.html', styleUrls: ['./example-viewer.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, imports: [forwardRef(() => DocViewer)], }) export class CodeSnippet { diff --git a/docs/src/app/shared/example-viewer/example-viewer.ts b/docs/src/app/shared/example-viewer/example-viewer.ts index 58bd3332342a..e8ca23cff8bc 100644 --- a/docs/src/app/shared/example-viewer/example-viewer.ts +++ b/docs/src/app/shared/example-viewer/example-viewer.ts @@ -7,7 +7,6 @@ */ import { - ChangeDetectionStrategy, Component, computed, ElementRef, @@ -44,7 +43,6 @@ const preferredExampleFileOrder = ['HTML', 'TS', 'CSS']; selector: 'example-viewer', templateUrl: './example-viewer.html', styleUrls: ['./example-viewer.scss'], - changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatIconButton, MatTooltip, diff --git a/docs/src/app/shared/guide-items/guide-items.ts b/docs/src/app/shared/guide-items/guide-items.ts index 489b65eb95cc..0ef4cd047d5b 100644 --- a/docs/src/app/shared/guide-items/guide-items.ts +++ b/docs/src/app/shared/guide-items/guide-items.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {ComponentType} from '@angular/cdk/portal'; export interface GuideItem { @@ -67,7 +67,7 @@ const GUIDES: GuideItem[] = [ }, ]; -@Service() +@Injectable({providedIn: 'root'}) export class GuideItems { getAllItems(): GuideItem[] { return GUIDES; diff --git a/docs/src/app/shared/header-tag-manager.ts b/docs/src/app/shared/header-tag-manager.ts index cc3939bffc55..7dec9c53c12e 100644 --- a/docs/src/app/shared/header-tag-manager.ts +++ b/docs/src/app/shared/header-tag-manager.ts @@ -7,14 +7,14 @@ */ import {DOCUMENT} from '@angular/common'; -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; const MAT_ANGULAR_DEV = 'https://material.angular.dev'; /** * Information about the deployment of this application. */ -@Service() +@Injectable({providedIn: 'root'}) export class HeaderTagManager { private readonly _document = inject(DOCUMENT); diff --git a/docs/src/app/shared/navbar/navbar.ts b/docs/src/app/shared/navbar/navbar.ts index 59f0b186f7df..a1b4d42d8679 100644 --- a/docs/src/app/shared/navbar/navbar.ts +++ b/docs/src/app/shared/navbar/navbar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, OnDestroy, inject} from '@angular/core'; +import {Component, OnDestroy, inject} from '@angular/core'; import {NgTemplateOutlet} from '@angular/common'; import {MatButton, MatIconButton} from '@angular/material/button'; import {RouterLink, RouterLinkActive} from '@angular/router'; @@ -24,7 +24,6 @@ const SECTIONS_KEYS = Object.keys(SECTIONS); selector: 'app-navbar', templateUrl: './navbar.html', styleUrls: ['./navbar.scss'], - changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatButton, MatIconButton, diff --git a/docs/src/app/shared/navigation-focus/navigation-focus.service.ts b/docs/src/app/shared/navigation-focus/navigation-focus.service.ts index 095cb4e064a0..5669f94859eb 100644 --- a/docs/src/app/shared/navigation-focus/navigation-focus.service.ts +++ b/docs/src/app/shared/navigation-focus/navigation-focus.service.ts @@ -6,12 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; import {Event, NavigationEnd, Router} from '@angular/router'; import {filter, skip} from 'rxjs/operators'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; -@Service() +@Injectable({ + providedIn: 'root', +}) export class NavigationFocusService { private _router = inject(Router); private _navigationFocusRequests: HTMLElement[] = []; diff --git a/docs/src/app/shared/stackblitz/stackblitz-button.ts b/docs/src/app/shared/stackblitz/stackblitz-button.ts index d3c57fd40ad7..923d99920e68 100644 --- a/docs/src/app/shared/stackblitz/stackblitz-button.ts +++ b/docs/src/app/shared/stackblitz/stackblitz-button.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, Input, NgZone, inject} from '@angular/core'; +import {Component, Input, NgZone, inject} from '@angular/core'; import {ExampleData} from '@angular/components-examples'; import {MatIconButton} from '@angular/material/button'; import {MatIcon} from '@angular/material/icon'; @@ -18,7 +18,6 @@ import {MatSnackBar} from '@angular/material/snack-bar'; selector: 'stackblitz-button', templateUrl: './stackblitz-button.html', imports: [MatIconButton, MatTooltip, MatIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) export class StackblitzButton { private _stackBlitzWriter = inject(StackBlitzWriter); diff --git a/docs/src/app/shared/stackblitz/stackblitz-writer.ts b/docs/src/app/shared/stackblitz/stackblitz-writer.ts index f9e6db6eddff..02e80c662cb4 100644 --- a/docs/src/app/shared/stackblitz/stackblitz-writer.ts +++ b/docs/src/app/shared/stackblitz/stackblitz-writer.ts @@ -7,7 +7,7 @@ */ import {HttpClient} from '@angular/common/http'; -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import type {ExampleData} from '@angular/components-examples'; import {Observable} from 'rxjs'; import {shareReplay} from 'rxjs/operators'; @@ -59,7 +59,7 @@ type FileDictionary = {[path: string]: string}; /** * StackBlitz writer, write example files to StackBlitz. */ -@Service() +@Injectable({providedIn: 'root'}) export class StackBlitzWriter { private _http = inject(HttpClient); private _ngZone = inject(NgZone); diff --git a/docs/src/app/shared/style-manager/style-manager.ts b/docs/src/app/shared/style-manager/style-manager.ts index 599faf88690b..171ef3251f5b 100644 --- a/docs/src/app/shared/style-manager/style-manager.ts +++ b/docs/src/app/shared/style-manager/style-manager.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; /** * Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be * removed or changed later. */ -@Service() +@Injectable({providedIn: 'root'}) export class StyleManager { /** * Set the stylesheet with the specified key. diff --git a/docs/src/app/shared/table-of-contents/table-of-contents.ts b/docs/src/app/shared/table-of-contents/table-of-contents.ts index 4b2841aaf82c..77b2fbfe7908 100644 --- a/docs/src/app/shared/table-of-contents/table-of-contents.ts +++ b/docs/src/app/shared/table-of-contents/table-of-contents.ts @@ -16,7 +16,6 @@ import { ChangeDetectorRef, input, inject, - ChangeDetectionStrategy, } from '@angular/core'; import {DOCUMENT} from '@angular/common'; import {ActivatedRoute, Router} from '@angular/router'; @@ -50,7 +49,6 @@ interface Link { selector: 'table-of-contents', styleUrls: ['./table-of-contents.scss'], templateUrl: './table-of-contents.html', - changeDetection: ChangeDetectionStrategy.Eager, }) export class TableOfContents implements OnInit, AfterViewInit, OnDestroy { private _router = inject(Router); diff --git a/docs/src/app/shared/theme-picker/theme-picker.ts b/docs/src/app/shared/theme-picker/theme-picker.ts index 740f1efe3e63..5ab8b5809d89 100644 --- a/docs/src/app/shared/theme-picker/theme-picker.ts +++ b/docs/src/app/shared/theme-picker/theme-picker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation, inject} from '@angular/core'; import {StyleManager} from '../style-manager'; import {DocsSiteTheme, ThemeStorage} from './theme-storage/theme-storage'; import {MatIconButton} from '@angular/material/button'; @@ -22,6 +22,7 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; selector: 'theme-picker', templateUrl: 'theme-picker.html', styleUrls: ['theme-picker.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [MatIconButton, MatTooltip, MatMenu, MatMenuItem, MatMenuTrigger, MatIcon], }) diff --git a/docs/src/app/shared/theme-picker/theme-storage/theme-storage.ts b/docs/src/app/shared/theme-picker/theme-storage/theme-storage.ts index 266c233b1f7f..2982ff0ceab5 100644 --- a/docs/src/app/shared/theme-picker/theme-storage/theme-storage.ts +++ b/docs/src/app/shared/theme-picker/theme-storage/theme-storage.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, EventEmitter} from '@angular/core'; +import {Injectable, EventEmitter} from '@angular/core'; export interface DocsSiteTheme { name: string; @@ -16,7 +16,7 @@ export interface DocsSiteTheme { isDefault?: boolean; } -@Service() +@Injectable({providedIn: 'root'}) export class ThemeStorage { static storageKey = 'docs-theme-storage-current-name'; diff --git a/docs/src/app/shared/version-picker/version-picker.ts b/docs/src/app/shared/version-picker/version-picker.ts index d31af44504d7..91cf759e38fe 100644 --- a/docs/src/app/shared/version-picker/version-picker.ts +++ b/docs/src/app/shared/version-picker/version-picker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, ViewEncapsulation, inject} from '@angular/core'; +import {Component, ViewEncapsulation, inject} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {AsyncPipe} from '@angular/common'; import {MatButton} from '@angular/material/button'; @@ -29,7 +29,6 @@ interface VersionInfo { styleUrls: ['./version-picker.scss'], imports: [MatButton, MatTooltip, MatMenu, MatMenuItem, MatIcon, MatMenuTrigger, AsyncPipe], encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.Eager, }) export class VersionPicker { private _http = inject(HttpClient); diff --git a/docs/src/assets/img/android-chrome-192x192.png b/docs/src/assets/img/android-chrome-192x192.png deleted file mode 100644 index c281c4ca299c36bd7266958e9652d5613a2affd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5690 zcmV-A7RBj_P);aYXp|K0@f3E^6Qz|D?dlVL^%Z7>4Di<-wCxUH@g9|`9_fe= z@AMvx@C!`S9kzlD@Ub80_8^?^8-jcZ^6(5(-5|Ju4DqlZ>GK|q(Hphy4PCt==6eY8 z^CG63811+s=jSEA_9C6m9JKKaQSKRkehTrZ9qacZobDyO)+E8v7pC|mqVOGyhTIqh7Rv_1@rVGjKvJ0{3y8fCY+ZS?fNOG)FZt5D#GR~%l0hK?Ix|u443~g+5a)v z|1sD9G1dJs*8ecm{xQz{G1C4q%le_FvJ-FvI;Y!u&7B{V%}$ zFTwpUz5Oq{{V%usF1h?JwEHc#`Yp2hEwB14t@$ge+b-ewDyH`;qt-3r_9>s@E5+q1 zztSw@=_s}JCz|CbxXvr&-YLTCCa>Tqz3(NZ?Io(qD&_DbpY$Y@L8x*ACTc8s_P$_*&?*PCg+wE9fn+Z?5`Bk8ju>h&3G_ZVo=9I(|Js<0vI_ZDOG7iIPp zX00IV_7q^MAM5lKUGWrO_7YsD9_;lHV)GDZ^buI39qjfHR__v8qa5w`4pa0EUGNZ9 z?hR?68}0NCQt=F8>kMt48twE8SoI7}?+#Jt3vulXT$>r~@eEG(3Q6?|Oqm$&@e4`x z2vGG2M)3z&@&{G(2U70`TkQv5>jz`y2yNmBbmItd=m%-(2WFQR@8Ae`&kK^?2ztg1 zpVA7B$qbwF2t?Njh1>{z%nO&=2!WIq@6-v2lNIoh7447{@QxGijS}#R5b=r-@P`lb zhYs(A4e){t@_!5RfC}$>3G;ji@OcOEcn0%z1oLnL^peQu(f|MeR&-KMQvd@A5E2_7 zAu2F4H9JE^Nls2#Utel#Zgh8fdxMFJlb4p9oSvhrudc4Iv$eUrz{SMK$jHji&d<-# z)YsYF-{Iim>FDU{>+J39@$mHY_xAg$q#l_701}o-L_t(|+RTwHVzYlhut+hLMIc?O0SghdnPS;u3SkiQiK;|a#bTjgE|8KhfggdDs9K3i&3geHOz)i&u zaEGz$;$e$NdF5|pfE&SN0!*h|Y6Jquz-JchKgP{SjC=-7+K+qrauOVCm|!{_FKq?^BAe zc*zpnqM#cW#QI-@v#W}=y9ticydhG4W1(^yDt5mF3-h=`;hO$~+8 zDrUyF@v39H9T8Di;>vA5fU7RL%A(6GTxF4O;0F-jMl*_#K+bvYGjpDM?$vnU`oj!Uo7dEHpYdi2Nx)aD9b8A}XnYi$@^*$146=WF!IcFkl0aKeBBD zMEk!dc6WDs(!gXcv1fnM=kw1RS|KLxlfMmc9sgZqZ_k}UGfT_*WPhul&07DFB*a8w z!1;=8+W@kDr2l?BFkfHgKPvov^4SiEjW%9k3P2TfssFC^-%kT``zn93Cs?2SCq`~T zY$OLL0N)@JK-MRp{~r18r+`WP{w4o0as1g_C&Wk_51`tiqCN@7|M0K|9A00?e|&sA zn;rKP5~zd3&rsGCiGv8=%Q{5aM@`X&_Ob1nHMb55cA53;t}6`qRS+xCsX2Gr|l& z){pfsJph+>QJ=z}4B|I(@WNoM?Idya?wXJ$OF18#^m{|0df*#YcH|5EAnv>sgQ=(m@Ayz%{E z|JhluP^&pr&9rKUwz))Uhs_EE>JveI^gm65OD)$YA3yt7^vU1r0tGFtsu7^SC{Y|h zus%!VkEFmQ@rU$hd!*_=GwaQL1q%B5Ktav6eMyr)2e9kQ{)8Cqgn0e4KiS*sLlc0J?Cd!N8TRd?&wmCn`bYyP zXe}bDHBNJhqQ4111K|3!{-FTfh3P}HOfNpvGT>gB0=I396h9^s?uUup3c~dP>zCm1{ z*gt2)BM8!E&p&JZ?Wb4x&u2ivt)(TCU{$roO`#IS4Y2-JA31*rK{~B}mcPAKd-m78 zqgyEe@eODg`d@s84{!jsJ}dg?=MNxAI|j(Izq~#3sE@SkR(Q!K7*yrH|Azc|1?&wv zC9FSk4}xSSCfJ{EKOa6)`1=+pXjn27S65eCKtXcI`7!B#1#ke?zij<4njlEC>yJeE zFD!I|h95|O)dbM3aa*874-V}O;`1l!<#C~YE*EkBtiQ0RTE^OyCBrIl2UM?WQ=%LI zCr}dp7ikDmNB)z}`bvMK3p8}Bm;~$V6yQ71@R)a)O4Rz7UH^-eJTLSQ9KYxKFA#s# zfr7-~3Sk5Mngj(YF44*goP2QnFOV7#Ogim9<9Xh!>u=}=4K2&d6ky#ZXwi+pS%HHi ze}~JgPb5I#nSZ*mzqM!o#Rkyu69u3I2nOf_4XK?*qOe?MqQ{I#(0=ZN)>48Z<#(jUoyhRoVpU<6xR zTdkm>(U}0{Ia112&h zF%SVUu#Pc&(j_z`@Ptf5lu390qxthlVFoOWl@Moe$^vB2;tHm~!h{Ow62<()1Vl+t z_I-`-d7Piv02ApF!2)U^V{EKocnS4shDBf$VjMUQ8IO?sg`{nP=UAXtuAu-MTp+Dh z>#Tid`>uiM@F{@wNFZzg;&_r@(rf-Km`IfbOs@iXiR@zx>$-$`Lk(<0oL!6`Km9_X zm;w_UC8ky@mBrwKXd&&kag1lV0Tqw}UKeneU-Rd{#H8>0EI^RJ<>e&{@Fi+b8DHsi z3HAOwyFs5J&Sv!d3q-NF046^AM5(3*C_r!n?KLnl=dO?}(ZL`PScf>-XZiD*KQ{sv z-sF5o%|QZ4c!%~zjTMAjwBPR!29K`@Y*74I{$d(TyqEa&(+X6eZ5-qIV1|3W{y-a8 zfjAjGeuU#A#`kE*`h-?+tra){<9E%-)`JtE2J|to1aVe1zUH?UFmqD%t3FZp>(1bs z1v(cUso))$SQLRCijR(({cqd~pS0tg#)$iP0vqg$9?1hj#9 zh_evSe{{48CK6S~$A4W23_1u3AjUIUas|i?i^1HB0=oNy{Ku1EVxdwMg=QW9M9_d5 zd@xqP0${}GY_8`(ghiT^qw3U??#bZLx7z{JXb3cS_bN%5H(Fp;bv zV$k%PT7h1ou6>Lr9$bE2qhc@taVCdu50eEO84(Z#Brv$Wyt|_UI$dN9Y^hX$m89dwOjJk1RZRqzI0pcrr@`K<+{UHnS5!40s( zujUPc0?y$6zKbkdD{u{H1&^R8yBZ$TBPp=)<(#Ng8jZ#k(PY@*76sUV1&rS{FslNt zf^ATg9L5)eVL6;K?};V zf2CsMwHIAjS#*;P%T^V(5LjT@3LsONv;xbNkMb!#5(21ypv#_n=gyp&n>&uDQm-!+ zkFQz3?>y&-JH`gdH$R95v$M0j0B`UM!WukL9bdxx1s1VYg;m7)fI1=N5Pgw)&cjZcBPK);)R1_>*mD}^W=%-YZp z{kt`O=2P%oJp&>=ClL+1!YLPb3xouff-wk3lg2>Kx$t={}dfC8GKM-YzZ z-U6NKA@Jl+G>4s_VJ}0(*IovRurt);Rqz;8ba?U)fpPSD3bd;)!<0YK21X$(cnB)m z-1v!MFbnxg*90n}B0tgr2GULn8bL*an|~aPgzgg-C`n*^P8S#lUFnc-LCq_Xf64-r z6$BTUN{|4f6^wwIaUq}q48;;xx&cr#oZxhWv2<21FqB{Fr0xZ1v;=m3=HQX z7H9`Gv1D=>5aSg*1U0S6WCxfF-Kv`ce;%I#gP|*3BdD>GU0^VDrL!Oax<{LgH3C6& zz@4BS?z!ild;SkZ+iIgVfEMBb)Psqi9Deqbv03Jm9D_R0M)`f=@lXMEgKKPn1$YBq;PMgZr=Q|eg$%K@w7kR%AiMyB z6*yBU_zJWWkp)oxIi8=fyp9DJq(C^373klBxG#PNzjH2`xh047^)-&8;Mgs2>lC6`TtNgP@gYn()LYRxrE`&%d^Iz!e}t3R*xX zJ-+w=c5t<=V zpmU8QX_4Mr0ORLad_0U&DNhA>gBlb>Ko9Xn2Wo(i9m#tU1$KD@pMqnR0*z}tD#g#a zk#J3B)&2x&yQn9$ZM-8egzyi81M14Ftevuse zxbXOI?NemzacWke=e;Ickjbrj^K(T0ZMOj_IH{)KfsPd}cv&s1BG{mqD>6L!x3~B3 z_zK7hc3Ul2k;cLeG| zw2y_S_?2=64;83YL4PyohE{U|;jaY@+74$WOiL{M=(x9wTFd73>5vx#D!K6I=CayxTf*pDI&0Gcb8YA-X zo4X3UfUH0seuGy*r9d(KCa;35U{fA`<5$72Kr#I0uL2ba7=H6t!EQjqf7fV;Z=wKs g`0v;%{MW<(5B4v|+!XO-uTxjZ3lRU1df?4VD8Uo5qjkf9ov(WX(#Fo9bUulPEVKvi`8R?A34cDnw>#Qa${y zMR~79<>Jy8vC`8b>vz$l$szMM=Sb6nr6ql)Z_Y@}%#bg3o0gB5y@-;S;wkO#G%iP^ zC-_UAexVg7_k z?yO1HF=%$w=t&PHpJ$xYY4ECz{ASdU`J3`=kn(WOg2}ghGHZTs#wepxzj)H<&KTux zul~as)2u9kgvtIaYunq+P@%4U6*y<01rQzQ5ApWN0@vJWR^1E)3i@ni$$)%O0A2^}DX zPbky-NXP$@j?Bo@hb2R2)EI-32gfDp^Qv^7Dt%OP?}X%zIh7qVvLSOy!IP4~(<&je zvO!alTjmu4rxmx(%59!d-aaYgJ44z$L-LzY*fJ}-aZ1jAR?25idOc4}ORxV$ikp;L zKP%-iMOr&RI@;Rr+zbE&A~$ag0Fvqj)!84+mt$Bk_KoWHhqwROXyRoIQ^GLp4*ADc z3}azKV*0-@fnhJPr5OBwnR2Wf!`cS^qmTdJi2wf5u)P0|{v=NPe<2gY#{M6TVWIy+ zV$a0%dhxFhtN&lI|F@$5;W__9{x9l#>z9WA#r_X@rvC+YUCa*uPt^aWzWo2pKXH!n z&PT?p0Ptk3U+Wea1&ww7xt3rfOXPyf$_bmHkjJBF1?F5#DB3@FO!9NnM%R-QpLVzD z`j3#k`q9mwy{I@lOiJWY=s| zJzx^N$SeC;Y6w{@C2p76x>~p@=2xFNe!1Qk{v2WCwf4uJ>D0@r{jXG`rp|1a+7-ut z^x6Q^S8|}_r%ai(Y=1_uK5@4E=kbM*6u0M+ZkHahqo-)SeVpUm5|>~Z5x!cIo`d6%)GVvgb-g7mkrBRpOB8JS(SH_VUYG*;o|D6 z%1PtZXu1R2Yf06LWEe^Op~9f;*!ajCUG#aSY!$(mbbP^YN8aM|sDlYD)*=ErHWxeI zCxmUOV|S9zjo8R~e5%zuG8AE!5jj!OjlC}33~%bC_%xJ7s^q-=z+N!&-sA>;wG%cI zy@W>JUVGsDqMy9_MqL3xk-gy{X@GoNu$Ervg8S0X)F@enu)_u`f#aa5~>vkXNrcwdd=qaFl zX_9C6a0pzXusNN;8v8SGcq;v^=qdjPVbiPAJJpcs^ws>UKtIe+;D=6{=PFcpolZlF zV3qhQ;llN-(~CiY_zVNmBX=m`43c@l5hy5_=u53kAN)BC@BPYJG2^EDYb%lK3~H&$ zvK#*J%Qki^5@l<5=2LAYG=YmH;BhO<@NV5A2^WxODUNN3ls%TCIH*$4ma71HziJ<*b`OiruB zgTTDg5EoCIU>Wq0E%p)S4)lC{g?WqmS`6zbw@iub0Z!>}>eQ~%tK1>f-Flu%=lOZV zpGu|n9Q5@|!z}&)p(mpo^S(UaveiJtHDLd{#DQKJwx$hnUbJ#EoO zCnIFtY>q~@2+B-puHaVO$9d+(G*-0GTR1)AX7}p~;I2ylJV0L154zmJFR!B}xe8zS z(CqY<0Us!0C!kw&bB-IEZ|$4V#}rS`g>}$Y{AfOj&wz_@!2+W=wDG?d`V8{mSYK4` z7&fAqWXt!g)N*)z-1z8+$Cmz^RUhL1#>y14iyvO}(zWr>B@z&lEOP#64l6CH3aK#@{lU0X71~(fzwS+dS@Wxg{YU@22GF_Mm-@?9A zeHIH=3s3V&MDAp|x*{2>K+l{${l->Js`m8p9+h%5nV(bZ!b=QoiQ3R$N$R(6FSWX2$UWW+jKCw~+~E(fsY-!j3ab)LiQ`KyNAt9H z;9Z33iW79wq_uETa`SFe4fqS|F{>dxtIlW(55Gx$J2FakM4zeAW?S0zBaE|7Xuz?e z%*hEsSSv0oUo<_PwW3OKE8w3oL#TMcObB}ZXQ)MD3XH%X=Gt(%^0nqFs^2GouoEeS zOIh2RTYF3DsEiqmgiXy&8=>R?56!s>%pAnx^3lVK=J7>V=sy7*!R429&r#EI@(Ec= z4W!38lglYHjU~kWxC4D0e%PUqK7}#A)i@!>>SO(!rSDW3Fx0o&9IE;6DBw+~)84|n zN7wbZ36IY2-YgQ9#>B}7V$_F|^EPg`ALuS%pLXC6Y59Bg=g?zODCGL5Xu2@{$d@~C z?JRvja>}Tqu$S}KtU>XS3oKKe&Vr&8;wO4BKA*qu>ZJC2hOl;nSh=Sh26jZA(hg^=yyp?PwJ^IJNk%k2t8#k_O zR5&$R)<^He!r#R8 z>FXu`<8Y_3tQGbCZHH-jpjspo(l^iImrwc$SXaYGODu^t9$L9GPs>1z3inJoxK>B4 z8q(NRaqhfV1f0V9OJ>8x+CbAoh8GHQ;N50tg^6*EEW|4V?Imkt+1gmels$HHNb7;m z<`H#vf)iwgjcyPI2onXPdjxQ=(h77Zb`C4smYcd_?43jc+(JNL(*?nVKr+1h!_voa zkBTP7Wt}s_)E25-BIeAokw_$rJpr5fVojl|MAj8+I$##|y3mA-?j~dC+S~iv1&3|i zJ|m-B2^ag57d@qN%lXNR8e9%G<#2C-Ucl8hPf4A(Lx{7gih9WUPb}U)JwvzcDKJ|Q z9ee#^418p^gn&u6EaVx$qHq73c^VR8mp74qAc|!^v?T19zSo<6PMrQf8lmy z2}J0mDl--Ju^m|TU1yJkZKK{L?R`1Ui=AA~Fc2G(>G`uqal(HV2=z@5IZ2Qy9Yq#7 z!6DLgA-)#Ah4OHL+T3aeku3!5d0}FHg3x7X8j-O9J>gkBoQ%3%*e6VP|2>09Bzqpa z?F@0rDwOlHV@QVzkF(!@LM{399>t*aDdTdIDJ#C#db2zyc6(RViYiP)-;0i zvPGZ$Ky_}xN6?4#IF~0)wpMsrsS_|oIIMh)>b{)OoQ1WbAKqWF_)#)fls=t~d6-Y% z$cD#3OAmdh47yN9{e*o|pADOLuBHBhKgMB?aciAwsV_ZOF*MO~A_d(m1CkL7ew{!8 ztFD`o0fLEsRw9R0kSNsv?f;EieZ~RAaXFUAOm9vE`W+iEoef*{g>SY9e~7Es2zH{b zOt~TB@rf0V6FMKmMXonNrFM!mG{W)H6oyDL`(@4r4ogdTVM_yX|E!@VkrGVI*eXoK z8avx_Zrh21c4BgT$=2|fb`>>pOxgmY{SIh3XG&|b@NYn=Y;0iQ{8sSPqL#G}U2+wU z+YxGRmP!r#fD&V*LmiOqN2#Cql31qotLyB*KDZQnJQQ|$s`^8;>=2c>2D*sd!9-M< zxt1LVh&JlF*xE`@R_xqhgXA}tx%2xQ`A_uF!9s2oR72|dbLY>J0?Q=Ixso%jCNWsbmji_cZWxKuZ2?EIpnlZcL7Ack{nK3}3%@l^q%% zGTAbivm8DDI9r;r)gn3dBb*~oL6<`{RFz7Up2hDpc?D)>$unA*AsJnV24S9hlYcnk z0j@Qj4?bY7nEheg+FT`!ol~-Yujy|VirNOnVd}OwylWp&tDPZ#5Q<}hgua|{v4BMq zB^Xa(2NuNR+!j`Agns@Tarwmh-Q7iqIe;ZgMJ(AgxEQ0-GiqzfYTtE~%CJN|W^-fk$GH|iPgWk@F6`3OFI=w~k_+b2t>Ys| zNjTCKn3q!owpQlB5XyRso?q3l)IAHVzmgR4nR)B~L?cmKXXgHxP zkOvj{AG6ql6J|4TUc@2vyEt*prNBaHl*OU;>tG3~aDCYl<|k}?^A)WG_&ToaF|MX^ z2c8Mp%n?XwV$)V#|5&-7`bP8-Q)4XL!qlZHQ!S~Ru2{U&i0>s6xwk=>k4qPICJ|R) z_wg84!Y(xA*%i#C!Tw_xXtGeWWY%)`(sc1ZRVb^`_`_}60!5>vIpdgR zGrZ*7_NaxBXX^jrr@eo?Cn2zhurn3wmIos4ze$A{X-kAR3nxzU!?<)CG$iH5$qQGZ z@CoqxnB+yFx1{2^jnH>7pog1TAQh@22f!@E?H$}RbZyajr zkKUfw$9KNh^<)rNyQn0HKDr3YuB)!S|=|gK0tH+j-WHdk-K+n@C%839^Gg z!QXqULb3a=frr}`{OA};6&{Te(HBIEbkvjz`+f!fS;_E862p>d3;B?WLXrXcpYUgd zW%P{Z!f&V=hCYS+F!KWJ?+Q7>^)b#WiC3Xu1uYPC5NJYgeXNIWN!bLY=8n^O_HcbN z_EAlnba}cmJf6$yxDT4f_p*8X&VT%Hs=@u`M@7jff0m5)wZwsbnQ}Xd>>ug)H?R+) zk`3mTM=-?dVf`9kFoQ74wQiI=%GrKJ1B)(|JBW z?+qqbf_1$le-Iu3_>YRhXeUGqobmyqsy4=Ap?xnY`_I-FydRCbPbNuR zxHiCgbMz?4N6v}n2vI7!*iSvoVciDWx^$R7Ro*il&JbM^k^g-K%Rej&7LWKb`PNY^ z%?HWg*P&SDZgVN$p=2yC4oire6?gPZ!{A^jj2>*zs@ho;jY?eLxloviz zw@dUg<_opI@ncaNZF1lJcsKy+rS=XStb_lQ;j%fa&_BN)gSl$ak}x&@6Gdkh$diA2wb$J`KY3VrJ<~u*2M=A7 zA|+)i-?L#_X%cSUD)l(-J@DB1+1@i|f0zHc`{w?>>pwb$zoIKguE&%$Z{2dNs(N9| zZJ0a%{oUa2Z^aViTOYQk{~CjQiLLsIWo#1g0}{+he%=RZi{GbsoDeD3ravb_qz-}x z(Muds&uWbk9#%(uueQvkqu*+&#prt9>AWKn6=>|iq7On{ zn=ya-aS z0qr14>n{j9Qd?36GcGc$fKxe`4;MDWKB_K`(q63oZw(^@KHE4Cgz z$Pt_g!GD=OA5!(4P-8|*?quspAZVT>%~TT9VK*lIL^7iXuV0-cXTnc&(40(x^&-FO z?gvz%pXgnc{5r0AGjF%+E&><_{Tz977wARVw(FDWf~q}UyO+*kdu_#Afp0}UV__nN z{#h&VaxvCM<6cyfG+UmAj~LF0Ms&pv>RIhnct+vxx1Ry^Vbh)USaKk?U3hZ)uGlD; zuD`psgs|2KOvHc1A#EzO?Akugt_`5x?~5?;5EgFvFK!5ScKq7i39OuNfz!Z`pBSM7 z_+i8-Z|qVZ$K=xd`22OzugQ|RMag%tKDsL8m@Ngz{VB0*1CcU=Rlfo6rJG-{*esv; zFSXQx!(NXL3$dtY*x)5}6I2A3x$qJM8rzR8c`&42`Z0vrMUAG-rUN@&?1r^LTJ+Vp z20wi)72e#7F(OI_I&p?jx*cZ=x`;+y1Q3qq9|B}%!g9_)CTv~X9fC8z@X4#>ka=UrkL#EZZGj36OAttk zvx~@Nmh8bI)5XlPhw~o;{^lgZ&40Kx4V5@i@lmdCvsf4L0_#8|cQRc!Pm<9nV2uf< zSpKYdsy#MZRwT?^m~iDCfp&Ash;qnOz!>^`G| z)d&X1{IG^TZWrzjsfd3I?82W$9YFaeZ~~+378=ztTynpz$bo;ORiX?26FIa>5xRbf zHwPt>o#>VjQIgD9Lv&7TRMQRpovH<+;`%sOg>HpPHp;rKj10BE=& zD1-ypS&)S!O)8|X$T_!aw~$IGaq!B;_P)l#PS53?oxs`L9VM$7*$i&SITe8o&n25Q z+<4XySIOTNz1rs;y2h|24ff|*hYycwOo${#I3s2uRVhj`G&@og!K|ZE^pXg65GK_D z8t@Y~S$IFaZMQH{_z+X*6bz)`41?|o6f^@N!dmesosXKu&YcOU$E1LH?TtZj=I4G5fDvK|BN6L4EN@d#c6lov| zD#QA{UxE+EMySJLlf(Y5jIx$ZJ}YC>5YS2BL>e3}*Zk;wwLNALZ(Gl?9XJ+onM11Re7Rxp4kLs40*=x{_>8%BhYa9)Ewv)p$47S zw;5if9#jWAMVW#@e&*@<0C8Pf9}awk^&w;ABy%mJ;~n9{P~;G132qzHM0fT58ANj} zY?%B(y5PvJiJTh2Fe?xZ?tacErWJTC^sLZKkQXNiI_F)L@@{uCDLo?$sIlM4-AhKJq|M)eRhu9hVG7S3Oq!#g z)!zK$1-cC_nWdJZQX*${k4OWA{m>#*y8A+gkL zli?F_<~e9V&Z`8CMt74!@Tl@Mlzo)D+KIjKhrPe4wfQJ;HG*D`Q@1G;yvk?A!}mp- zh2fG8ac|pL>97%`0kx(pb2}w2c<%Ubrs&4=NH*>iy#I3adU(ok?6sYu8yr#IB3Vax z?%6F1U=A^<%8OAyt5VkKLpT{^9P%OZ*C{KGmT{=-71-Oy$Tt?Phy3Ozwu682YH#8f zAR8y#X6P8vp?}3i!6Ru<1S9&2C{a+tM_Hep$=9Ag#nxiXpVS}HyNY}bv5!xsBa#%^ zEaBu`zmAe~$REBe6ZRJ_Z?(=DCb#o3G}hF3`bj%n#Oi>5jCr18E!=R+h2RAy&M9M*R*?1Vr@RfEYSp?ln;{RQRUaS#LDQ zog#cm7_#TJtcIB4?DN(J_oLIq=KKuH=3_@qqP}MPm~H|?7^&nm)L0d668J*8IThYL zl9Ph`oK!$rvEiD?(KoRDQ%+lXjuYl`HoC&MPF)eo2&-O(D78v+e@Jw1!p6W%8SS)6 zt`t`aEZU=0ix(iTd3x;p;qi2ZXcv}CL~vHWtju*Vi%((>1mR9EQ;8^m9eyn>N*GAn zAa-jD>!^Imi8W5?qr$q?Pp89I>CVTb^e`-GSI<@I;F}A8rZoh!R-G ztP}-dVI_BNG|1H_$MIR5Cf3)M39{b=FP<}Rzv&2>w3xx^T*fZ(_%eo6TLv2`hU3t} zFob}ZFyD*55$vScp{s>|Sm84cGc8l;(mcnvU$=^)hB>d|89!bUvV4Is=qS#$t_-|J zs~R(!IVVLU^!z!$Vhb$FAKcRI1pCz(4Xb8l%7n`-|E9swGVCojp-7aHe@~9MY_r$a zBiDuGu0B@CjdTYLRnU`Rx9vNz+~8vXzYZ_rwaC6o)JcDWVllIOnGfN3F{aZVk5gAg zYq$MSp64kvh&q3ZQa@4gfJv98P4IZ7YQMVzMMY2^scMUq|6=n|?w$uYiug1nnWie9 zc3CCKk#Xmv1+387aFM4VS{FNs{i;g4nUW%N3o)=nB3yOvS62>E(>KNwIm)HZCLirH z4nk452(>0}O~5kQ(=*J8mSt^ z8`y9{--ddA7;a1o%vE|wG2s&{XW`REc1&+9bXMV-FnyS=*_Ym@A2p84T1nGvU>(%) z60P#CO3MqHXP3`}KcS6-CKvy+i_}>x`apHXAq+nh{+E9IH$M40b(yL@6eQ&+`GnAP z?iTo-uFq_smO!jsePlHnzs(M`(nm372i(HfSeZ!vrLjqhor0qnv=W#AG3w3ga2DEk z72k$5fxy9xCf<2+wcC9c{kE0mPfmlSe^FHt$*QzD@@5G2Zqh1kKeeAO%z{&hZVw(n zP>nTDQVB9A;u#Pymc5ZHdpT^zm6)_0s{o#_6Eh$}*;0;Oma+d|#eeA+65^qC&H$JdJa;xMA|1Hxk=kARh zSkcMS7RqSFmVEl68Vos5$T)3C8H|7&=u4M&mTMq7KtS->e+f*ykkF-!Uhe)5A1Z$X zZJ1@LV-puyLs$xD*)oZa!sAsX5=;X1jVqQPuMWK!LgH1QpuMX8f71ak9NGbnh?nZ< z&4qC4hEuQBqgoY{LHvP*iG_fl!Zd8( zb%emPk62J;3fB=ajVr94Gr|YMfu1j~Q2R$&Q#oJ5a)eORB__bs&$J}1|BuygF74OG%COqroV3oNAf?R{Cl^Ios4QPV8{K*thURqqfK?n^Y)*`HN{hBPAogaHfiDT zI#;rj2E+M-Rpi6jwI$^)5iZ5;@8%s59S0gGbY0e z@d<~y`F{w%NTmT>`ek>@Ldk7$_bHTDLKgsM(m`k|jDlQGYf<+JF|Dj8AIl#eHYhcc zaf#6Sl@4CgCMdj3m%oKM$hCgN+Rw0A+VDlah~d*{^smHO`DPcis{xzVdJXj9Wp$j< z?ej+Pp_QKwCJ6U$a@Ijw^xwX;TH?(7zCwmOStnni=tUkipe;PbAFCvC39W-UQZz|T zH~_B5tEFPb*sAQ9h50ANeP^bG^T|d_xXT8T*&F?~;zAlrwg(3WEJ$L@awlx#>BXQD z|6YPA|Mn)9T@+n#tJ00o-^WSa2&T#i`F~3w<{_(P#kr1hGedg=QvcKBT4X;>Mu!)o z4z-I^^ZdqKO1IPYsUX7d&=%oxfIjzB82dgmbut>A#<~OV zwfb~$U*|q0#tVYK%#b3Jc<0Ll)x6;;1GXumMe#$H_V`Jf!>(90U6yz1&*I4o@Ft<1 zP%nDhL>5*{KD>HLo2|pH1$pchmG=1U8Ntl^wqjRqpWKJcg}saONXt}9e6|=^An!Y% z7@hd+Ig|Sa64Do~Ly#~ykzU2BY6+EYcYo-hB;K~CID-Xm@kU(=f2Yb3kB&}-XbE=5 zJQD1;6k_KzWLAYVz^Aq>G)||lb14qFf1dT-H3j~H9zr`rI&!X9+5?{S0V(9qXA_!V zf;v>P?;wtL54x&>6Ae%mLa9U=p{Za6wsa%VrY!+|*cFNTI(S*^WYDZIlOrOXE6ptV zta?sjb@hr*oC>%{cI_i(4M zp^Gh4S9PdUh3l^iUgwWN1)!@vQ8mBiGNH5+Tqf(Bt5Rl{fe`Ns0`nqg=C8a2h?YgV z2{w`+G~m2Tt8mjU*HW2|8!_E?Mtmc9eiaTKd0`c#}v{LtV67*etvk$ zYRuu$sdT^Tcj)cz`9JAF{VoyOP7iDF5@hTe=8+e{eqip*Od}!qR}z;c5{%e5_Ay*N z*${T5mEcP3K@0YUQJ7~?5PKW_;3^z=$$D6F5k9bzc*tisCC(6Ds8z>n!lwb1@Y6p) zZ6`jjGD0n5N)4e(AozYmM5%OxWH8ZRI4fsUEcE>@G!VlBWkx?L_{T(pgdk__2y$hr zjC5f4LBgwbzSC^at1ffvH$kUJ=CF&zHLURn@_malK^xu#7v}X~pE#bZ!cDz!={21F z)yaU%WN&^r8`2xFPElg$i2((*CXT2?E7V8?I}u56xy*<2)Kk{q+fVKdpJm;lZs}!F zl|vvk7axSwWv>va%95Tjfi9DnyH+WzRlglwiRVi*l~HRg$U#27fqrwOuxI!>s|U75 zC0m0pmmue5ZdB&KOfk_P8d{D7u(d(^jhm;a#6v%K5?xP9L`bnod^bA&tu#Ye7O?YK z(04cyJBB`-?zUV zz(?XSdk!U=V_#JBuZ}|8?{0)r2^-w|Pj00q<>RKbD7jqyAJz%aa7}-_5Y{*F6AHS? zTE+eRN6tTXt|W&4ynr0Q3{AhX@dJKefiX@K_d*NWwiS0oI;oQ#H;(+XM_8obZ}h}| zDMYbrnHKlBNMcJM^`DdQrD;I!mGpafJHnN>_Biv_iJ2>qI?fK9`q`u{U!mLpYgb?=cDD10!M1~yK zq9tCQ`WRvaxf%AsXYi?4T_;#fJF zImvJcLtwNAeBBly=i^rcwGgl`TI>{HjPU5&A0LZWu)8^Os5*$6B1R#7I0nq<L4;e7uP6db7$m`+gBZQ z@HIcV8=tqh4Dc2B?nsM4#BdmhMl=Drw@ekpS#j$I-SQvga24wb^)KAHTaQZwKVFSh zl5gH)+JLy9TSe#&iP?Z8O}?38;4{Fk1vmyNT>L~yd{-?uIBwh<@^dlyV|=f0>T z>qwLu0AZKLW%6RGmW`qwm)wz`wzcKNe({m z=5-w>r#cVZy%b41{0>6o`<-79nG1k>m~ij%IVGOk<#szmF|>FfyiP5q9IYfFt^^XO z#tlzY*wvHEErkc;Csp%z2eNbYV#RJm+(rKL@uU8o%CyP@V4D`piEA~phTEN~455+_ zZtNm1yR<=%msVi?z2jjE{BU0Q^kdFp+jWN$e%~hodZj@W!ZVRt=KSKE(^#azCxuJ+ zei!g~9nUR-u==$W*NMG#H$vW^*Lq{*@O>$o)H${p9B`YpiebKPM8lJ_nkLwmc?gUZ zTP=;>sm=9oiDzYp-hVD}LH~#VJEFqokNP4N5^rO1G51GCA5>zS_;OcZ8LVNDOkaEs z*+iZ=@o9WAk?Np=j4I2w$`0*GIcJ1ilWiTOcibL|yWDPsCHlj%s>38jQM`ot1=g|i zIZI2sBmT7L0#TNsy(#aV@8FB#JhqAPjI}*(Z9O6r3iypA|NHl|?Vs;C%)o*^O{eeI zfNEKSb=i1a1syUoqr!x_={nX@@`DK}%Ok#y|6I@Qe3JvUMqF;++ZQfxu7wEjvNpHss6QH4ph6o+*7eOH z!*z#{F<4!A3m^Vk@`DTO4^Gb;WMnFMBhC`EFGJdn=Rzb)<@r}gSsQ0xSM%x;j=73G zcT2P`)?)MBAXD3_kZm2f{8aMO#k>2Ek%x-1$Wr0+btM7$**84EK0T%r?#brZFUl_2 zu#Twk)DkQyTv+f!wLl4AEjJsKX@)@Fd>u04kO|3x5!DoRMPXN~|K;|;clbNvrP;HE zF}rKVyI1?B*LZnFXcm|(%g=!(rDHvQbFb7CSbIgkes#d@U!td1`KbCG_g{>lcROma z1_}M}zQzdU;i8A*&D$)7=t@|1%9MTYd!tEEuj(A+( z{sq9nhw}5HD+u5!m!v36+;yU`LBi2=66mv6VEv*PDW>QPAgdJGfOt`+L z@3O^*tYFFm1FmI{V!c;v`!ze?33Ex!qIjmaXaspI>8kDfW|=WmI9qZB**QHuMr`Dq zDizv3K<~fb^teheUw~6^42BI8(e||)+_}^<1^bXk-ZX4kV+l(_|2ov5LW!hV;NI+g zBbNN9sN@F}FMJ3OwjMaq6?Sfku{l>6DK(&YGtgMH-~A!!LaXcRf6RH|m{ zdtcCbK-c_LJsi@;+8tPMou9aCBa|_}Mx#_}>uD$x+=CQdPubFBX>43GU}oU+xE^_nS{uV%!YOWUM5nXPVPW_R)DvoelGnc+%1@A z2R&Bq_Z+H6N)iN#*Ly*QJfTRNpJY@c=>bB9c0g zXDHfq>wz!wU=|Gf(cn3@=PZe@>`L5}0$Cbzt7}>x65iet`L&m{lI4N3$Iav_q`k~D zdvI%G&!ZtRQ`oIis!lprG{$F%Z| zQ)2;-S+a1?=Ci~dBk7NF=1+16Mw{rB53+|W#uM=TKDa5qX@sq}Z8xgW6yUY=9 z$8?z&G%DO#`ltbAUmd&#gqbTSpH-?CDDGES->Y-dL+<)R?$OKk+)#AX&5heL>**dRSpvDY<-BiXNJWSz zg;lr)KR2pa&bs6A(z8!zSf`?T-Lb8{u|(BL;$7S;(q=P#E&CZyNn6S%bwmjv@N9t~ zF?PW0!D|VUA*C4%u0-9d`5GeUL64jFDIj0d7&I&O?5AD8aehDri8E#Ln##sh`+iS} z47_Pz1E#UZ^(A_@1!=h|VLsf_JN-jyiuI%0q0mku@!%H1OaxZYDs>mxkuj;r4Be%| zj+JB{Ajp>QJoJX$cY@$^EYV8=!Nwd<4Yke0|nYE7?wZ~N+Q-#V+FFaKnAHdtR!|UBwsfTb^8rW?>1E9xEbDH-?pim$L@*9G+M7vRWnK9*mc zpYHw_-iBTH`WPB6s3EMn%MA8KvTcE-Q3SGWF80loflU$~k&Pcvrk&awxhkHo<4N1M z>#X3>gP17n{E|lvJ?-8|!k3I_D<<{3^pDj>LyS;W$b2Js!S4D3G$2L-Da+?{++$My zZF8r1G@jpHo%r=}rD{%k%=XrnT_{f6{pdT{__NjWULUj=wut;`;0YS7Z4pC+&xl+l zxed z86PV$e4E6dZmOB&_Og0^aq^y_vitOFK+8pNFBfA{iclF1s24PWHu@Xn_Agw%1Xa8( zzrh!JWrIhkYvawzBrZa6w(n!Q9dW&KwBHhf>3?RgE_GCH+K&-?`^5Jz5_J99(Aws- z?WyVn3l>)oh*f0+4f`DpLtX;BNMr%xf_B9?TtWJ}qKOMnXEPfWnrDM(9po=%IhS=A z?(FC0OxIDrQXxTp7cunq^&Eysss+--DMRGAo1}~O?6XXGT23^f&>loP4K`j>;AIXB z`bKQ(-v|99-d0jupT1_=5Ew&iVJ#@hfpF+&Nwtm#d$21-ZG>Yrsxab_`E*zYAp%{5 z9xto77FCM-AoCW~^AUkE^pcH#yp*f(sB!Xw63?PTya&4swryus^tTSRF(wz11V|V- znowd|-vhO|4la`AAqgq4IgiC6HYjEVA4^>pLSLDJ1mkyHM$J9*;g3TnKo5kSjf%qzcmr;TbBq^?!qaNhJLC*I@nYb7dX@=oAqc1!HaYe|6AK8_kTd-yn&z3Qs@?V-;bpX)#V2r(t@lqk-p1 z6mZHyJZ=lTO&;Klajo(EDsAM1KvJGzYW_Ddj!-00UY+6~e$#REpR(uAC5*>&`4MXS z*2aZip&wMpAdo5vjR9P)$dg@gY2bl{6eW{$JRRg~?Yu%+5Rf9IXd{xQS>=V7K^cLk z&NG(CAn^}$i>~{BfV5lbQ`8KyyrVJJ8$F0EB%G(~8-6>9P!xe~IgEg}M<=l#~0 z!F9lATWkk?QLk&P;=`wq6z-lMRoY%wmPW`1Lxymsq2~GC?#2H~96?cy2-t5YoVkP< zOktjrRT;sWe zjcJ)$1u_TAb1{#T355b%ejk4m+X~maofl@~DkHe?d&z^u6VTs5cu*pw6)qE6;^w?g zoh8s-;f}o(xg|maT9AO)^{FDg9FzS@4y*^yDi56Z&Mm~wVvlsp>t8FkS<@o1=6CA} zM`-GXjI_Y4iywP5@ ze77Iw)P-YRXL3(|Tc~sL_h>FL;$DvYlZ8!+q6beiz24kx)fyIL0NX#f8&^M#k{k_o zl3h{HCEWXGG&HM7tyb)%Gw5-l^Ms85R`t#xj+}F$I@@KCbp|btS1U@A_v&2zg+u*2 zQI_?h_G9HLUirlgqmOws{f(>^rzrtDgD!jpJ07JDEbsL5b;^kWp^e$%V)CZeHBDE&jEDn;oj*Ys=P`YLC?A~dvvbHn% zV$@ajACFGyLMC=2;_FNoXDBC?N-uagizI1>GTv=CKuq(^s*Jh|u{DT6t@*Vq?2L0p z#fAe?X?s?P{XNr=-9s1Wry}BAQjsN*?|aaS-)g1F&v;r+6$CliJtmO}8_EB2TuMzE z)6>hA0z5m?Br>VlSKl(YX2THazK6ut!xRs8kFQK?wei+9(L*~lfye#s_rN~&!BjZm zM(dAD!T;j5Ad|>LzaTa#*gbDQ=qgUy+WMnZa^+#n40o&b$7Qb|w&m6}GDB8f_`AhP zu1u4ur^uF=O+4vm%_rn)NRIALu}xK0Ti2K!n3ZdK*eA8UOgi-I2j z`EuWbCnE=gpV)uQ{@WB0c6KRCZKisw>bm2q4$4=b9K5yv?5>M1td?v_iT{b;^;0;T zpX)Hl(c1YZ?TyF&J1=sNetVO9#AO4$Av@RFUWIjILXb^eF%86v`WY|3IBGPYkK$*FTkeWxTU$IDN9jUwyofBalx&Vq z9e=VxzVS$U2;#3U0rDm1JbXcNr6l`?67NxHq+!l8&R+IaPxk5D*^oFlR%C!WQRLCR z?EylhyDRCG(d9oVS*3pT{@ZhFixA&FGG$S&FgR^ zN0cx|jCL*=Uws@srALWT3Vsi5R5MF*X10QF9&GPjq*qJ0_wLn=_xS-4%%jt^apOBlR@NLdrKb2`4)sU%V_7dnB!vcpp?H>b#5?;{7zz75~j ze>>E)0#y!P_D(AMJ)Y~BWrLeaHn>hFy|Q*=IQ(`V+=s->cpfRq;R`kQVJVg5(tiWW zA-{2g=2mDZ2!C(Hli3D+SK{SN{bvqRFW!+#_q4E4uhGHxqBqgY8Jn|tB+$!38pV9 zFOf#0g)*<%*A(-rjkx^_Mht;{=}u@lb!sA3$$R7zv+xz_C7rd)d+O5VbHAwHG}*3E zBpdw3cqMRC$z~W!{AC}3NBeJ=eENH5+y4oi3S;#Q_~+z{MTGsA8G4O@tE-+Eb3OVM z@cF00AS zM!-fd1lnr=o3Go)KUL%#U!?`EM6c|XS|hHnmDg^9_z_SOB>l=f0)p2W0qwhx4fq=I zv!eY$p_)+(g!cjcRsYcU>pgIth&@e!)tM%M)+H2X01fc65%A65g*5&;huwJ@^v}ug zFV1V`Q#HMo{a=&epUnPOD=m!xTHw;823Wr82>93xKh0Og!*2#+Obmd**0X+49npiO zKR*9FuCCPq%Cq3XzlT7z0iHDi+IJyO^VzO<*VVI$LI>D-)<1>;a81q9!K}1;HvDrJ z$Rhxj@LD5a(`#V60%HGy1V0x2L-@x%N1m<1_0{z?Wu29x&m$nXbPv4#2xu<^p5}v{ zdn1vr9LNIs&{JyuuTuqTu?fQeD*HFJ4tV}s;r2q{DgF}PSxzDKiYm5Fsc`LD)9((T z1?T`%X@D1h8`fS3Ji$Aw_eLV$SpxNnOuYqeuCJBl7Pv*f7TEvWjezz-pw1_h?=tos z`S4#9?GMHKKDMJMwbG7!TA*NGAy#JM2$=qDSbHH*<+u1JQjvd7zUl>Q$lNK|uFBll zzjiI}$XB^n7#?y9+%f@_XX*g+W+R}z5UBAX+`Eu*_O%h2JCbr&=m3PfjFD%R`}_N` z{(I~T$|^H$1Wb4<4DA|N<%=f(@|6PrKqHL&D@F16vm8#DkGzM+O>j?1_M?vcSN;@y z@wZ|17Xs}Hm=8aNZAkh_E%4zt>%%{bhp>Kgb1L<%?bJleX9=J*-Xx9+gX8hl4RAyhu5%-`atKA zz2OfK@ct(uR!LaUU%0am_b5aVK=pnLsHwmRuP;P9gn_!mYQ0ja+P^LHVhTMsuu z`%JH&)tSdaAmt~3?;26Yat-A(QIrElz^UJcy`GKxE1+kBRR2lz?4!8DPx&bH574C! zn;=w}0gf|`fP4*feE3JwKVq`Me<%q3hY+m)ngy_md{UCA4Z2H2#4?<11YEsj$^7sz z#RNQ@6hI2$?|=aG9pGOu{&n;#<+{Wu>XD#=`%8_0{6fGnwc3Y-eH7JhPd*E+hZU-y zgCCJ1M#@K`%qoC_JbVP4{%u(PF60JxjC*Z{;zbou1I#l4{hJ+tTBj(74G>8cXP}a( z1{i4se3->6cVM0V0V|6| zwQHt{f&QZ(0jKYPQ9ciSJDXR2qJQt*?Eu!a{D`Wy<@lolDPDe@Uw#CbYhdPz>XF3n zgCGk`79lLc1j`d;t)O-KkA_D3ToS_Ph;xnr_hE>>5Xk&87=MK2SuBun54?QRhA94# z#y9L+nnnub&m#i<=TdfWfDu0r)fWQ!OAvSHy?>m06pjEcp~Z@nk8sqD#9BXKoi`2S z6X};60r?t8ZYQm4l?Yfo{Uo5Pekm182_#CtM&qrx%z2h(8Eu( ztNcZaKyT@vtbVC?tqp+tB;Dmlz=QlIz+VMu9#Fe34{WoE^##g+nfn{vcgPO@yX_-j z><>eJ%*q?{q z&kz6OuL8aL$6^)tSg6`*AM|qUo9?<3|41qq|Af6WobQ#;pZVc`)K%awk*~=ZXiZ0z z2K}r5^ju@fCNTb$%_CsUZ^QD#|M;tb@eic6)NX0GIP`m}zrJQO@|BB>fcyF3f67&$ z_g4T1k9Zv_-{D{BK}7TBmb8M zAots_e;wg(Fa^|qXo^6%y@P(ix&qYyAB4Zf6u|n!B|)H|Cm;Zjc?A&uMpIxc|DgZi z{B)22m>;}#1rYv*Qvm)8A`o!iB;yGSDj@sYFv8z>3ZMh@;Q#%-`N4Jt5dP+WPkO~Z ztjCoQfP}YU7s9{96u{n#{5P)v!oS!Q2m%6JZ~F=${EJS3$aLch_(k{^o&w=rVjKV@ zfbieJ6bK>oWh#L1-^&yz!hoOW6SbcyV4eWNf0t9hDgc*HCu&c6zAQbS3akkL;eW^}u=rsJ;eXI6u&e?I z{{v5fk1K%iZ)6J02Y~QzWeUs(fbef<3d9K@{F|Br?pB0-sU(s2=S9G1k{G;H4ex^)b@#G1ciY*#0roq8sh`G0>hG?fx*( z?=a8gFxUGq&FL`F-7wqFL+6ko(UkSBK<R`!2coEw}P2wZ$ph>nXR>D9e5c@ZBiDdkFIUFTUnl$=4^t_bjr^ zC(gGg;`1r3?kBBx2J`n}xB4xw_$#i(CDHP8$i5}mnI!CV1oQbTsriV+&m_dLB;)%n zwD&2c+(Wm54DN9P^zkO7*Ce`)5$pOYq^%_8_)Dp60QJTp$BGc_hYs!fDWCQzpUoh> zwjtKMAI`Amy(f+4Uxt^rF-HCzQk-zo;DDp&jPy8;|)Wkn|;yyBWi> z8qUxft;ZR)_#}+;B#rernD!!wj}q#c7v=dQh`tuNo)+HnB8lY~k>41Z))}Lv6w{*= z)|(dM_#%Yx8-~agtfv#tpcUGd7U=XLgz_SW&J?4t62kT$f$0~B))boiqsQA4k+l)J ztrEtn6Uz7?fAb%G@gRZvf35c(d*c&`zYeRi5x(#jeCic}k`(E?53slpwv-j<^&54< z4yE`VcFPQy$PApt4WZHsjm`^`?;CR035M?-cKBhG^&D{aLV@red-ED?@)&6D8E*D4 zaq|{o?HFn77G>NBe()4u^b%O$2zKNMZtE0Z@DW()2WRyTQtSs|>k(P+2U_nAQuPW= z@&{G)2T|z`Q}zi-?h8ru2}bD)Oz{Up=mpL`g(JRCocb*5{HM zF%U&zGB`)_#0~HNiau)XaJNh45oq>+|9^F>wRkqAIn8E^p`}_`>)0l?rQL4#DfF4X z&{z5v`c6NrpVlw>{nnq*Uu$noH7`WwBeBe&WfodyBg-7gG>8-P;RDUbWmIeEl_RHS zp+%NCCNo9zMD$|zPJJAP)^TLeh33OFGs!%5(A$m7I_Qw@S!R)Aj!ea3zw>c%9MmhR z)1k+XBl84hK20&7{m%Kn92)xK|8nG?pmsi;PUU8;9FsY+N6nXH=ij`W<TlBo(i^Q~>@!1l*MH}geHr{>%fgiHnIvzb9F%oXYo z;=0D%tQ!rz)6l4sztJQ!Wz5~W?aiU|qVF|#UH71vAUku9I>yXK z<{vvZ>;L`dPH63%n*BM;Oa-SQbZ_5b<%;6c&8i8Fy+YD&q0qS}h?$Dc+0=X@8?(91 zOJeJy^$IWkINn{yUFU3-?F_i>EzmvWSX~WfEBP%1?Jqich1qkar>BA^NP*n)v8sg3 zCzu<5(LyJGIUG7?&Y&gsJL9#bj}bkv$uFH=w9Fojdi)8>OpmS1H}(t`wk0vuZnayoG}w=7+0<)w9lF!* zu-ZB_%$(k{$J{I-Rx=Vab`~|`Km=={tUGb(M?Ykpc~b!HCO+y|M$2QQ== zn;){d#%NUM%o8nt-+MO(~=%9VykAfqmZ)~WAy)}`-p15!4Iwmu!7GzTmW-a5!lZJv_T+|Y`4oHNX*wArYcJ?syt&=JV|lgOurB4%gmdzpIsZfgBb zHj39_GlQ(=Gv8ORNIujDBi}GUdeRav9rJa>+0ycV@B=D_r^GU}>zrXe^REoEHasUw z=Yc8V#xKUTF%KO@SX#`JdWPOUAxkGy9U5q#`OIflG13RaGBn76oJTFGaZvNttd7Bk zdYkKzwUZgpzvy)o$vLwft*22i>w{6O9VnX3No|a&L5^aCr4?Oilf4jZy=b=?6~t=R zZ(zp8gW;$Zr=8+ zw9^c<%j|kE3Uv*64srnvHHW1qiwV%2>(2Bk0b0oxa~@qV>x1EG&zR@Q+?W%+k2nIE zKUjLP;Nw_Sc6vlUHq?5~CPPLlw(i^t$=6^te=TgN=el$>&M%#|4RvLwF(6;z?7j}I zWHrBWd<9{Cj0eM8e{RCk#q-dq5ih+Bw0~-OO5Q5DXl8W5tnXZFKqVTyo4@Bz+K1#r za+#@h>Z|xFMT5QIU;H;lRHDJsVFp^wg=^lUGWFkcb76})VfFZu$=A_!6x~EOG1x}U zRd%?2D$~?%=ImzFq7seXG5LK;<{wVfh|S!MPK}{Cpbtu-ZDdsZ3c~!nJo@Opl(Efy zTrydvzA%!Us754xlzoBQhX}9S_Vj*@J=>b#~QxOozt|v>`{#nw`)5 zuq{YFXmVi214Uyz&^5?Ljp;DsA?eKg+x=#Pj*?B!Z(hG?`>UcQ0U~ly7j4xrBT+Tn zgM%E8{->F1Qv@9)1MM3Ppx?Z?nYA7aP+Sl%P7r(ypf=`DmR$N=g84JeqJ}xUYJ-l~ zjQq^`%phl@I|@u-sxE#g7NIU0cBJW2r0Kzas6wiJ&LBqJpNgmb<@{Sbv8O~ z1{r8gcFY;*9)}!NOY)sxpe{o#0XtE3>kajZDcUg`v?G?ZUZ_#Ds2ON? znwf!CYuJ&U`hAO?baFCOn^c2}u$5Y0;to}H3FvX4 zr=@E_*ZMWP#eVZ|i;kOt_ScLI|5ed=jJg^awZ^p3Y@3F9TF9t+BBSa%_8t-)HP7b! zX68=n);L)=i8hU*m3oH1&0a-BqrXj)-Cq;-HBqOoO`Y-Sh>&N2*5Zg+vs3@VIYX}S zIOgI5`)0IN&jW3%hFS8jg{P^-#L+Z^Jqt9&o}L#67TeNFV9`2D^`%N>$+DUE_Iq?^ zIodSQlA1)#$3lNTZ8Npgv9iLEwmr}OC+CV4dvS3wZ8z_yWQwvb2XOY`IN8ja|t z*HhM}E;h}gInpH4tkG}Wp3~8HHVC$8vZ1bsn(WwEJ!qn8ux-^;+p1rsY^Lr=lPuZH z!Rt3qcKcT)Yu4%p51VG$i4UdargSZ4qPJ+CrnyG*dP>z#WIDFrzTFY@iba#WxtTNb zY@@}Clan3(RVi*}NTym&cVe1!)vj95Q2VQXEJcfYN1KOeW&|B;xnNtC_|XrHcvsHe z(w-9QstHfL#5&7V?-RYX6^_S&n(6iRK0liGaA)U7IMT{Yv^5t(Gc%J?G|9}KNh8dh z^O@hIdS0ui88m&M(cTU96`I!5VVfqIXloA1>FAr5OtfM**Xpc4eSWcsI$MX?dZk{N%ZTgT+8}zAXZR#ajkF?t~%~X$tWHHw#-Bl^t z^O4H23Dq6mWkr#R23yS2L?0}iL4E30eP>mVv>#nyB4%0bL6+P}E&2%LT@fFJWH#OA zL$^uOCz{Qw4~2aQwU~i+tL|J~B^qX^X|`x<9y4Z1T_ySd&#xKsF3CvfZk#uc-D}8< zoQV1a=6#|;K4jC9Q^~V@ax+b$e*tY~nqt??Bx}1c2CS@_~IYC&pQln|0 z&oCZPccr1Bra6jMW=D*y-Aoj{L9P`e=a=V4=LpRkDSPIf<SEJxSHh;F zXk`XBPKLTCv6Hgpkc^yDP4WMZkHbsgTt#EP+Bas?v_ij1Y^LFKD0EbvOPE8ogYHST z%%(3x_4$me4X+{t^_88FXw!fC{sB5PhvX$$I)T23uS);tw63<>I=(oLpZEELX_ecX zGQ({iq0G!IhdGf9l8#AoK*x2&MmbU}2=mkQw`Y6y9)YLuo8zm}XMNYW8Aog~8*v=x zXoA^K?T&uRL4!T9U(rT=sweex25oOgk2>fhe}$3xtJsetUH@4n>*BOTJYn{8zg73O zF&p)-Q1bPh{qTUyaHLidvm8qbQ@XMLgjz{eA+KITIon7~eKM>Pe^xjS<>?p}xSkqc63h zO=U%oe}%izgB~*E1AXWuK3Xc3`mZ1T)D-5Zi9I*E@-ES03c*}JbvJxxO&@dV!Knk7-*PI`T{dU@;K2jx7)|epVLq;8 zs}57i)MI~zK4wxAn#`(J^c(tHF+=FxQU{-Y`o1A+WwV0^ppI$izRU!+Wctz%{1y6v zLniehP0i*>LX&!SR#HFkLqA&8fo2|8b?jj5AU!$lE9QZ8pIA4S46)Bf|8n?MlnXLX zXjb$6SU3gs9{V30qAR^?>Oe2FlNmdJBX5agy<}$3@xq;Dc&BAqY|-qD8KZ4!Y;uCs zXZe0m>M?nl{^m&?u(+3u(Tj6v=c$yAyX3j}ToT7wH`=nZyQwiQ4KrWrbCml0?CcAI z_N5N=HnW(_^tw0u@6J<6z%*-Cc9_fMcGyvh%o!;Sbe7DEf?i_t{Jg0DID7Ww$>oIw zpf5Zu&uLF;TUsLXjuKm5KtGF_*lc!|GYj(tY8}yRX-RF>MPjqspi4%51n85Denp=p>spv5@olc!em+z$|Rcs=0J=YH5jEBegA!)F{pBC-`ML<4YYl zw?iu9@Q!I{fN4EoS(yfPL?ia;ifJwCYE@Dj^*lzUOMF4jom-aFjP|4sq_ZX49Zwuh zte8K4!L{r4ikKJWam0*|&8jXIIW^3jn#~THQ$IK3OD$;4%uC1{=-p(B#i8Ei933&s zxHPR2RVt?|l}grZ)l~y+Q?Eh2zCO8u`*Ci0dExwdpr19*=^0<@z%>(C$n-<#|0a^8 z4skTH6MAz~(Y5l{mQ`b5XDejRF0L+Cfv$3DOzuaiw7#)Hsh4f)A$pn43`i~NVBp;# zGVLP%`HNhcPS_K9(^C*i36QvW?a3Dxt9=v7P&|y$y7C>W@e9oNoJ4 z2fLH;M8-4kGk|%yP{4HD&~<#2x>hSUk-8a0tyZfN5xJ6u8fMI(IW^YN)QpzYcsA{6 z`%(vD7O{{CJsJ-~W`i6$W*vd?nKkRRrdcb3y2Yu9jnY7in(jx3(31MBrp62P?WP_ZzS8!k z4%}Zs5!)pmNM|w-7jgxF=_}_3d3jgsO?+TubgemcgV|jdh=0ntbUcR2R>ed%?(Y>r5SyB@U6b$O&#=H z=ai7KFq1A6@^;3|YPa!q`oTcPnsjI|nxE8f&W&vh0u zt$+!-jDcL{#Q3gnUa!*u83UMpFj4awEk}Ku)F{nrPQ5)K^#yP0z;@;dnP$iZ78`So zBG)*x0n^80aF4pFYPleHz`j}Zr-s^-TF}zjCDWLUkN)5QTJMAd?p5Dy-W}BX9;<=A zdGi*hhWcNm4zzPh%pxxEz1RYnr#D$9W>dwk%8Jyr24L zJ{oHz5Z$is#@qL)r<`gvF1oZi#XquFH>+BJoNbm-;>9#7%c7gRS}Dk?tD03SbkYoM zf=rKo9@AOmLJwUut6C^C&pCz6d#=C?FsbdDZP%O=J@nBJsP$JZ(x$;;HoJ83{pFHb z#-6L+?`r0iQPpCNnZfv|xb!NOhWs63Q`KmCM;Ep~zGvm50F1+-JOtCArOmkN| zg>9==YDTA+U0fCR5|_*}mOlEzplYC*oG>rba2rSkr(xrO%yD-0bMnKWYLPw-MP8-trTbv+YV=9WS=dn)B!qx_Yp zYNbw~v*yfR65I3ZJ&R?Aw)CqOS>J*UHZS54nb|&$vul`wcGV(d&TP&i`+mt3UA)n) zTBOgJ!A!ot_wE@i^KfK}KKiL!waDyQnL(z&4Kk;^Yn)fQRg0`|!33D1#MHF;Jbv6P zs;+KXwOCiP68rR20vzA5OvqLN|9ELuE!3P9S(!PHh>yc)nWp|v9Uqh`|waC*m zf$@-VAzrZkvevib(fd2(TDxk&HD{S0Rfrry8p6!cnRfBp(-Y9zRWo`sIrKv$@`Er? ziSf3bw)Lg;1hh%jBA+>g8JEOS6yV?!iQ*uRdD=P0+-d=BR<+n{=9UATNQJ;rJWo(Q z$~)#3#k6X%$sBT`kjSAj)1e!Hop0L3U+uOuC#$~Ns%nw>Ey!dxC&-emvU%E;)26&Y z)go8ToiOS|oldk&7l9N{W&N~wn!TPhQ_QLsIm{j04ldBJcj?ewA@}L@OOaoK(g}~*pCU=L>8eGxSq)=VJjN%$IP>&zNQz63 z^I*v_&t@=hQ}yrO${4p{7>1%eP=TJjhv?QjWG;}YgVfqo90esm6V|Q}*@MkTO7p|V zxAn0lYU+G~pKZ>ANT-Bju*`)<23;Va4HaSCA?qriLG7NE%m6MB(`Zm_z2SiPc zb1!Bf!iZVGowcTt2nu`+R+Bq->kMIF)}not;mIaA zYFeqYThCBkS)nq7>tsZ=nl}A5X2Lq^EU2|I$%aNv>vcw(U07hds&K6gZ6m7H)HpXW zyRsU~QK~MyL2T5J?$#N~z?DG-t{vKH>etzw-B-C?tDahK*zl;S_X@(fk%snz`XI8^ z)PDtA=Np9Y)>Sq-YL{NAvzJ+1u@0oeTTT5sL;FuzXGx8T`h5{KJ%0t;He1Mzc6h7l kS+QX#M?IG*`CHwIP%l^5wX2GAgY5LFiX#DqK8vIpgkj50hM`Qxbz{5%Q z&9-oz`+NO=dy?!-0+&guJ69k}G8lrv6yCdrT7g+`r`n<+7m89eP6)i6bmq?^y+`l1mF^k_2P=b7J$R) z4x+ydtZt`XK~|C}m++kEA0r#z{dW#s_h#zXpO2GJ@F8J(DK>9dnBS4K4zh{!^z0cYf+u|Jn~D zY9IY^W_1EFxCq3$e;hvC68cXFebwQM@Nb>E;sf>jJXEzODEPRTqNg9On?F?h#Qdp2 zZu95*Ib&Zp$aeGd_F2z;-aFI2kH=@&^5>QyTs=Wv0C@DVV?7;RaqLg;k13!m9)z6uiOS8@YSX?Th57=;9n zjZj>-HD!S@4j&BRhdJ4cP#L_hyZ*dM^7aeswmc!T?J^gsB_%Zkka0000< KMNUMnLSTX?P)1Jx diff --git a/docs/src/assets/img/favicon-32x32.png b/docs/src/assets/img/favicon-32x32.png deleted file mode 100644 index eb09cf74c67c57b3fe455b53de4b0789959b3ea6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1447 zcmV;Y1z7rtP)n|miS6of0aPJq(_b-puB)zkMU9{x?He&BJcg8J%%v*rp<{E1(ze$ny5S?vcqSQ389V za@Z?Ld_{}~3ZgWS9ifGVVYR*K~4%5NB5FKC_$i-OcQ~0fy;O>mG;7&c|G=$HLdRqZ}6n9GH7D@o&Sy=fq_^cqk zjy?kjK{$%M1kX4DHe%HwrdBiEnhhdl0k$8j&wc?uGf)px0`z6HfHUcU3E>ESoO$la z_tPZYoqh;#4tN3-$_|dXDLppUqd-)ZV4X;1r>dvn{(6|;tG`YQIO4Y}=&DKZQ8IR7 z`6Z?{mn9r5dYEv$h-uAofqEkf;C+7%H^3O4={PN5i`!86GW-y@()R%lBTpMwNH=_0 zm2?0~5|6=U(c$krt;1=U8?ACLd?tYKQ5*vdXBoHA0$3>pM=-{bn17q8GKF~k-s*&1 zP)zKFTg2g=Olz|uG<&iAl)!Md2?jPa=hFf;(ww7I)y}lN;7HpE4$<_#J#APE0hrgmf63JG5n~&A+5lh2w%BnSOfeI; z=%OcYRz%I-fGG>lT6;Hg*_vw+>+?7IlzV(-|Ff)V`e;G+Y`GmxS=WW`5A~BmS83p)ab)n27GJ*dXgH|mPlFv!G_o|1=OOC z-v!-h6rlw$)GPN>WVzqvK(9X)V+=7}2y0~54phvco!VN%y0kX<_h^B^LtN}Tx(Vh6 zXI7*0{H#VylM(Y)0;Y(rn=BDs0Ama5K*k)>^-6TcN16{|f|v3o!%dmubykA@rE*>H}ZQ-iI3 zZvjnE%Q1$K)__4M#_HdgjEUD6jO@msWwinYt230d`@YnL6MifDy|;imu;~hIP%|(D zG$Ld1{hf}P{TrBAe}SIW)I%3?mwu2lp!NInC#~P_cMSZW0b}9(3^m;E@jET?au))_P)i+;i@5rU;qFcb2T8w`tGy-Szih+gwi!pt;5a>z;b=(+zO{hNsp&dFA2$qZ^*y z{m}a9-~8+)-#%w02r4I0;`u`86ytO2zI;J{^P?Z|tN!;1UAihoKCt0H{lNNzdSt_#?p-(Y zgS#)A{_{I8KJ;oDZb!wUI9(P^uQpPx|N3iu^J9PajPVoN`UB~zy^3dy=PeIc+WH;U zn;)8@`5dRo&ZeRdu0Q;_y_-(xzt(-Px@X<@>#tpQQ1@QGps&7qX7RA5 z)xn=FNC_Y?#&;I%8h4vA2dFpx_Zy4yoa@cXx06O&_;KV%GEfARKqz|Ty1EH~sVPpE z@m=EqFrTZNLcGf;3dvHf(0t5zD2)xLwb!}ZwRgO7_Rd;%$NK^bh!O=Nz(hizny9)T2Vjb`X2JdAhQBp16NUp07NT@S<=vb`>M-`k zPSJYfMHjr~fm>qc<{Z^O0A2IVdjLM1+OPOM@=;Kg0c(u2Bn8630ui2Uq;Q|V??Vr6 z{h93uAUHdcVfxx-hwmw^^SaqQ@3pL`!^mN{76l%LhukrehKf-eVK(%9G|D$q^aMn=k@Ic=tvi7~hpRdkjz|eM~9{u_Ors$gylb3mhvVtms9*f5JsS zDg#IW;m3PmVAhy%kb{DzppAKtWM!c|`{kQI%FW4HV?B@MxHs)n%VbR0Cv5>8H}BB zK7cnc?f@~9ZkzY{eJvY0Sp}f!?PgABQI;bpD37fKpfxg$vQQ-9t9m^zNqE!yZ9@G| z0@D|azpDhqvoOU}0qBWK@BaCHm*4Y?fSR^vLmN3z0<(SBt!+k;2w_MNvSt#qEG5gT zCE{cS^1`7k^@GOz$zBhHgIoy~dw;s_9lI4^anWM{6B7V1(i*ycD7CI{+6FS-RH0|} zr{-erpjoR7p_M8~gdi&sGONStewDf_0YrBW)j7-)a!DDHVMzhFlR6Oj)4=!n>0d7Z z4IVo9p-aDdal&7R@w$B%f92JH4<1VB8`BS!z(JRNYRJZjz)zVAVMLg+V;)wMijx6a zbf%&1Ck3oFE(c{9Q7XFKx}LrTfT@4oMU)|>-m!v<9$%J00a0QTqLwqZM$9C%1bG!qzHiORjESD&oF@uQFA zh=q&^$uS&QnY@L3zK(jf*|bfFK{OL}xN7~}sHID@D)(6MVaiKhqS3cB+w{Z$_J9E9 zt-iTZf5FU(Xv%=Vg(3Qayp%D zLlEyPq$-bEyBJF4yH@TUj6Z$gMG5#%NMCB(SW@5uBq15XW&w79CD;x?c-;pF zbAe;HP>t8KP48b}48#n`-+%d`^H6M?k1iHDC1osXl5$$`SrqY07rneZ56FNqfGe77 z75PTV5fCAS2@(aGr6JLP`X|6?O- z9F|Im`c|OZY?;Mi62P$8Z?sgFMR?JHG=x%n=|~#3MOiAT%e6LKc2ap=GmH|0(ecCQ zlt2&pCpxJ^vIsLsRvrJLIgq4dsE=6;)w%n<>FIr+`%#-VkO8@SJ})!u%80oGEz|yj zSW=1aH(NaSXO#DMuzg)W{ymd6-S@J;KOe{f8(ak(V6Hg!d8hZC($cUjD2rl-hLVOT z(%~Fg9ZKuv?q`5QFI_)T0-3=GV9kjOj?GO!`L^h{-A4MR`SaAJfiX_tj4{KZrE_TK z;nX>F#W9uEi%8!@r9{c2YFQ!Qu>eAquotSdn#d%07#vjy?3PFJ910&>k^z9;)Nps7OZz`hiY44s!w@szFdL7 z+Pd(nk9}gLDnDjSpaS4Sqw^pA;K;Ept12)l054;|=jty2U|TSl)xi@##4b){ylEZ8 zHH>q27?Z(Zn1TT4dG(Rej?F9wK&AmNn@;D26wfRAkBgCH=E)Y z%+yVaM68|(K?FojcofPiRZ*Ddl8OS+rb;8~rxCAgWKC&9cK~;7IllaC1|!&hiboS? zi~;Ij@Vnpa6hl`N3nz1{Q2-tg22mhNaxANg5~o9D!z`p>T6P|X3V#5&nzO*f1ZO}2 zc=si1uW6UX&Wc=HSW1dj0UU)P2Pu$BvP$Y);8x*g5 z){>ZPD_*l8C(LrPR7i!10+bX=UeGRwY?uWr@2{*q349UYMe{E$VY>t33^6gub0EQP z1AGdKw~WlazoqyQVe`n=`%{Zi?7T9!JP(Z}jOeq&wOALHpTz@mA;`Ef#wmoyw*^Y-<<(YJlIH~Nmd iyQ32uPr2uL9wk_t#StC$PMNw2iF$feUz0&JvC2@#Gqe)CU+9oE%j6ZYQPRAsj5HmVP zK~xYy1{HBcK}1jx#VLq_${+<8KtQPRy;l<^?0+7n{KgIi{P1;yyhqn+$#_WM4}t-Lq-b(X;c>#^>e#aAUJW4KoCNGXb6qC zQBTDkT@N|+DkeLaif7uf#eWv`D26(C`bcxo6c5w3$wvfI4HL* zbGZ;?%g=M3@R&J@V`dC9V5XY^k2|xN>!`&mjve^OsSazLPcoT;A`e?Wo*Kh!etIN> zXNEDD@2WC9#ZtV+2veSEFZCt#t!~NF!p}g)8Cc86U%i|UYF-t!dbMM|dhWGcC zSLcl7UhOm1c{R<*^13i1Sy=gtIkY|r?=r%bOTUn^DPIJ$SJOpswfcYncHjAQ)~*v? zLArMXwtLmo@^v@cMd_|;-}P5n--MJ^A%-2RWGoUw*f*{1e3>R{e?8m31-ty}kndOh z`mJr90alew$3s!+qzW!<(FN~;L9!hdNp|zWXp6Qu z+Jw?*1CB*8I32B9XzhP{sSZ85P%AqGR+bNwbT@i~bG1hmmgN-+v+lIQh_eb7H8DCR z9t8a&e|h1-Qb(=)xRMDy0z=3_m}UEP7TNCQe6Qnw#PIV9<6E^8xyzShb!d*&F8JkW zfj}QwwSWmf2`;SsvRPjI7G!zDZ|{S6*wffq?Gsh&a-Yt1u(pF@FeI#i1{AcI0j-NFS!>5ig#ZMV#VyC##ysjoZ;%vfnCnIJ!a+vMF;Awje zp0)4BOZHdswtX`eJJhmYIi8E(Bsl&L-$kq6p_H@6nPrcT!88&4&8Xop&jo#DDkP|MJ#oy#5$K_*RouW$c8f4ul0FF0H@ib z*|u3DIXpI;jGjNU+-Y7{4o|pnnBz>(6DJ*>byQ)Y<2AhL*oMD3HDZZ#4OY3F!A94k zZOMYlUpz;CZ5~^7b4D{C&Kbqx$&qBkDP9j2PrI}DlN*CSyXx_riv}+`_uzHs4!rMt z1)sPy;2W3oNOV1gO&&)-wB>s!Z3~7oW1ktr8s?5-FqfX6^M=#B!)RU)9bRzP;3YQ| zUU%)nyRL0m?AnASuC-X{R)u8u6NZf8r^gOuj=}moH->xU*)d$)^P_lU;+`MQ#KjHQ z#=YvHjeFfg9rw0JXWR!Kt#O}vG{r6TxD=P@aW*c^!6z9I}?2eZA)Wx7CH zv0VX~fjzz(dv(6+!1*S^vRAK{JatMnQnLf#uubiOP@Lhpd`t}AYkuR!8QNX3XBERZzTVpx{wkLH5 zY=I^q15Cgs7(^*Bi;~~n7-m|sG0ce6FatKxdOcmvVsj{K%9x5#_OSHqJ5^aM{lm(X7_(sttSOPaL$p|whW`rA&No)Nq!muTb!`3hc zIdXQjEuTe_=En|8lqi$blU;nwR^Kjh-s*1gE@;Htp%2^wPMk@5jC7a-(-OBvn6k)_ z6Jf;m2m^M6bJ#_Dj@{vmu|Upw4dj>hP4?x%%ElAH4UPQkf%>B)t_Chb&1x}I+W6(C3gR)@>$~v$&%2JURWybyqCKS+q>A(a74o={3 zD3U=*q@F8}W+`j%>(?ns;|ShYOAm4C-~-SF?*qf{uYz~NM0+f=WE*-V z*%kTGmbwGcW*m$%;ZT$jB~b<(iQ-Te#iD$I9>*u>z8T2>VD+ky(r;9kXepSGVsNy#G|CRZL>Mfxy)-}VQ)N5r z@iZJNVl8t{D9q)j6efI6c2;4)IR%Gm1%nGQdR&ar=vtz*eA;M8mp1C$D2@E2PA5MO zMt&4r=wTQ_55dImZ$k=b-u>m4kUeww{pYO?&)(OoI3H`utd2EWF2r)U6w9Jcp+|#4 zi$;a^(_8!6daaG9{8Sfq3QSl9Sa~@N@=_Q>ODrb&q0HWpqCs<3wF-lG{Y1l(hFG?{ zF;QpGhk_7ZU{eq%@kI?1fk{L);>cmTONdK z^S#W%mRM~~YpfdWv8s;-8>@?~{)maF0u%9ljUoK>LOj@VGygN#*n!&l91-OXCUs6! zrFAF<&S_B|#hsaOA&rZyo^)$oX&}G|Q|-9I zN62OfnEUxUTVs9?ZBGl}Jjv1Y&_fQU)XDb7Zaxn~c_7TR(|12^uTA}vpywgJ&+h^2 z?HC?#Q~Oc=DD7cKbJxR;roJf-Mm$3JAGK%kn4KO^P%h~iJ5|?wd)1~m!BxN8$NxLt z)4g<<>SpwN*xB+S<)3t9nB-)}WJeRG68>rSbnglOY|tCq$=z5r$<_ShWET^NgTNHBseBHNxQIs`D7QoSA16-Y zDavKdvsdxl1m&~Z@rM1CAK$lctXk|?x9BrLrOkc8-HdO#04|RVH@q}unBme?50iPS zJK?4L+f>TGO{Ey7Ia4l-a&V6l{+Wb-wgcsd2>+k#)tGPJgBR>O@e1L8%f1DR94_Ny z$4llf9nW1_;dJ_?1i}C2chfw`D1SGN@^{l+ z2{+~NW>5?>ojA;LWbp)_M|99(o`V|C5&jnm|LYEIc*mg`e{*cWXO0)}wNn*VJD=*? z;Cj3~+qpz;o$qd!v&R_kpE-)XK9ll(Ge(e63_SNV!-I_YC!T|P%$0J1l!JWSnZuJ# zES{n~v9gOUC$ud^|)oT$I+>G3+F(7p73PHKRuc$dwMkCq}*-4ji9{kFv9FXd1JD9?u6To za;a`wyx^+FLKnjBat&{|bPzY$iodxu@wr^pYbBuOCH(`$|GmI?xD(f)4e<69rw+05_R!t?*V?TtWfz$?Pv|N78+pCOEQH+g?~ljnU?q>Xzgkegy{ z|Gk&y@hhfqUQ6SwIEg2C=N%DK@t!Z-taZQF627b481EGoocFSKtQh|isQ=`>K>d%4 zM9j^YtZl!ylJQy<$9S#yk$8$vcx?PLb&;6)_OCowA!ce8hunEi=(mcI@mV`o>-Aj? zsp8EKw%$QXYS#6=Mw zRMnj~?#?)Pl`v&;k8qt?>yrq^XAKNKt8yVp7!fbmk69w+3YG-3=r@Yv3kg$}e2cDT zF#LS`Dxa=TzVqq88lS636t-cV@CPIdo000R?AIh^dDmjA7jao$=dsJPs{gk)6u*2Q zUU${)v$0I&vjIAvB#@4Y?*Ezs`Y#&uwS>LrYZ+Ji6>&^o^G0!eBVm4ADq)`*s(-%U zI$@7&t#3!`I^T9|@NGqk&lPO)X+nmu5!u3eY!hC>4k2-2!tCBJxm159fxcTqje&l<9w~lT zQ&aug`Zf`Vw%NA@nZ8Zf;&T}}KK0n?a}j%dYWnv1o=>IU@J)TzVE)yT>=B)Qo6q$4 zra|ME0=?e`uzu?Zf6^I4K(hDBaHHSK5U!34tBCL8wPn1mma_WqLiF?K)3Y0F4r_aQ zK--h)0j;`B{~y3>(YE<3k?VK4Uz4`i?;`g3RqOJ7&p&DHKUm4 zLrWSqy?+Wg|Mf8XC)Giiq(wx& zxDkbc4JZn%MTzJ_(*YZtmKC$L-P}vc@4W_VL^28R-29-!`{0X;V_UMETP(NyASSlKLdRZlM0R*LMW% zf1J2!ZI5_2)Z(4cigUq;xAkjS4dN`~h%n&&rea40+8}5!^JRLfWW4DQ!@n4z8VXJH~)@0wsv*ge6FJ;ammCF8Zl`}+081{9FSxFC{6A=$wQJ&Ggr zTkqs~c1GSf|E#QWzBQ)7N)T0aE%?yZ?%+bGf(xLP?1PT@eMYjI`2C&z8fKFuhdB1F zyp|cgL7548QI;&8=if(`f0OGsqL5>{^)(jjb{G}6Elucl8G*XZ9NL@jr1TNM&?^em{)O(~y3VNg^&`1xFwxAGt zX#rv14=#8w=^OTtzF-&3!8>kf4Cr&XF4I^^h7(nYA$vcupt>pEEx7}5o>q>nI2_rWOL z+pl3aOLr0e9i)-iPCA90%0n^crs8M|N=T=0gx5-tR-ugWm;bDnI8HH~h~{uIn!zcG z;Y^hNr^+Z@O646q4+g`}^ULax@)U~UCvC_P=t4>edlBjG3&F_>NZ-KgEB2Ay&^K5p zhF&SfAkA$mQCOOf#8~=D340k?IoUCaq2g!F#Yxg!oF>i1_oVx%q!`Xd>v`=(XQe_D zF&M6)wrfI8M5sfLcWOe9682K)NgKk393pK-5otpT34cMqKEo`_BYno68yXGiu0GOf zG#`nw%q~;(rj#ozZO0U5(Affgk4xo$sfXpGeloE1;DhgFdtj{Fwzdw1mzr9O^&2U<@fFeMdf>Vc<0;dAKgy+tw@H zn?mOqX7l6u??P#eUUD+l@-g9VKTR>*(3==hMflH?Rr6;hNOy9P>{5&lbuk)a-Cg($ zG!c~%8u=-sR(=9{!q4kcSklJa(54iVKBVZTK80dvx5)N=te59WKx+&h`|a21doi|h zqH$4`!gPr=D@;wSv9Bi9K)6Y_LNU}5{(6ND_4FAu#HhDZ>^60oxBI{KJ%2t$9e!rJ zHtaO?VJAt~a-4K6$G}kx2Kf=fez?yRTFjW_2QL{z4lDv`k8T}r>vg4q8CNsWH2>m6 zH&RtWtW~bF--U=uwf_Y zyvlJH!gy^?S+`MsWQ$2&GM|y|x2fM8Xbyh=R!?VT>J(f=LoBzlaU!c%68@$eXJ*i% z(EQY*P_JqEKWJfE=((nitXiXwIQtV5{ypJ84X*#ZO@*EdJGPRGI2r-j@!|Y4c_4gu zT7TibYo5*bvR}4L)Yo5$)f#_@)u1g_eWgQj=NfG$q9#NidHxC$ah5bel}0w=Og-s> zzU0DB-L*3!zZM_=nKa+84QQLFd$}!Ev$uVsT5~nF=ND(Ter??Cb-Jhv%b3V&4QZP8 zQVcJfqN@UaYi*I1v~OMa>Hp9bt4e6Uuif`{j03$_M^^i?6D}nfBdhg85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( diff --git a/docs/src/assets/img/manifest.json b/docs/src/assets/img/manifest.json new file mode 100644 index 000000000000..482bebc3d65e --- /dev/null +++ b/docs/src/assets/img/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Angular Material", + "short_name": "Angular Material", + "theme_color": "#FFFFFF", + "background_color": "#d7e3ff", + "start_url": "/", + "display": "standalone", + "icons": [] +} diff --git a/docs/src/assets/img/safari-pinned-tab.svg b/docs/src/assets/img/safari-pinned-tab.svg deleted file mode 100644 index 6b6b3640ae57..000000000000 --- a/docs/src/assets/img/safari-pinned-tab.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/src/assets/img/site.webmanifest b/docs/src/assets/img/site.webmanifest deleted file mode 100644 index 2568e3bccba9..000000000000 --- a/docs/src/assets/img/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Angular Material", - "short_name": "Angular Material", - "icons": [ - { - "src": "/assets/img/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/assets/img/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#FFFFFF", - "background_color": "#d7e3ff", - "display": "standalone" -} diff --git a/docs/src/assets/stackblitz/package-lock.json b/docs/src/assets/stackblitz/package-lock.json index 8a170a1986de..594a6d55f9eb 100644 --- a/docs/src/assets/stackblitz/package-lock.json +++ b/docs/src/assets/stackblitz/package-lock.json @@ -8,25 +8,25 @@ "name": "example-app", "version": "0.0.0", "dependencies": { - "@angular/cdk": "^22.0.0", - "@angular/common": "^22.0.0", - "@angular/compiler": "^22.0.0", - "@angular/core": "^22.0.0", - "@angular/forms": "^22.0.0", - "@angular/localize": "^22.0.0", - "@angular/material": "^22.0.0", - "@angular/material-luxon-adapter": "^22.0.0", - "@angular/platform-browser": "^22.0.0", - "@angular/router": "^22.0.0", + "@angular/cdk": "^21.1.0", + "@angular/common": "^21.1.0", + "@angular/compiler": "^21.1.0", + "@angular/core": "^21.1.0", + "@angular/forms": "^21.1.0", + "@angular/localize": "^21.1.0", + "@angular/material": "^21.1.0", + "@angular/material-luxon-adapter": "^21.1.0", + "@angular/platform-browser": "^21.1.0", + "@angular/router": "^21.1.0", "luxon": "^3.7.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", - "zone.js": "~0.16.2" + "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/build": "^22.0.0", - "@angular/cli": "^22.0.0", - "@angular/compiler-cli": "^22.0.0", + "@angular/build": "^21.1.0", + "@angular/cli": "^21.1.0", + "@angular/compiler-cli": "^21.1.0", "@types/jasmine": "~5.1.0", "@types/luxon": "^3.0.0", "@types/node": "^12.11.1", @@ -36,61 +36,61 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "~6.0.3" + "typescript": "~5.9.2" } }, "node_modules/@algolia/abtesting": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.18.0.tgz", - "integrity": "sha512-8siuLG+FIns1AjZ/g2SDVwHz9S+ObacDQISEJvS8XsNei1zl3FXqfqQrBpmrG7ACWCyesXHbicMJtvRbg00FEw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.12.2.tgz", + "integrity": "sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-abtesting": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.52.0.tgz", - "integrity": "sha512-wtwPgyPmO7b7sQPVgoK29c1VpfS08DnnJCmxX/oU1pV2DlMRJCzQcLN7JSloYpodyKHwM8+9wOzlAM0co3TDmA==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz", + "integrity": "sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.52.0.tgz", - "integrity": "sha512-9KY36bRl4AH7RjqSeDDOKnjsz4IxQFBEOB8/fWmEbdQe+Isbs5jGzVJu9NEPQ1Tgwxlf8Uf07Swj3jZyMNUZ2g==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.46.2.tgz", + "integrity": "sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.52.0.tgz", - "integrity": "sha512-3a/qM3dzJqqfTx7Yrw7uGQ98I3Q0rDfb4Vkv0wEzko96l7YQMxfBVz/VbLq2N+c59GweYv6Vhp8mPeqnWJSITw==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.46.2.tgz", + "integrity": "sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==", "dev": true, "license": "MIT", "engines": { @@ -98,151 +98,151 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.52.0.tgz", - "integrity": "sha512-Rki7ACbMcvbQW0BuM84x9dkGHY47ABmv4jU6tYssat2k02p3mIUms2YOLUAMeknhmnFsj6lb6ZzOXdMWMyc1sA==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.46.2.tgz", + "integrity": "sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.52.0.tgz", - "integrity": "sha512-96s4Uzc3kk+/f4jJXIVVGWP5XlngOGNQ1x6hW9AT59pOixHlOs5tqJg+ZUS/GQ6h/iYP0ceQcmxDQeLyCLTaDQ==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.46.2.tgz", + "integrity": "sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.52.0.tgz", - "integrity": "sha512-lqeycNpSPe5Qa0OUWpejVvYQjQWV5nQuLT0a4aq7XzRAvCxprV/6Lf841EygdD2nrFnuS58ok7Au1uOtXzpnkg==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz", + "integrity": "sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.52.0.tgz", - "integrity": "sha512-ly1wETVGRo30cx61O7fetESN+ElL9c9K+bD/AVgnT1ar4c6v+/Yqjrhdtu6Fm4D0s4NZP081Isf6tunH1wUXHg==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.2.tgz", + "integrity": "sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.52.0.tgz", - "integrity": "sha512-U4EeTvgmluRjj39ykZSAd5X+a6LD5m7/mcOWDmB7hqm1R6QY0yT8jLxpNVEjYhzgEN5hcDGW6X67EWQY8KiYGQ==", + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.46.2.tgz", + "integrity": "sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.52.0.tgz", - "integrity": "sha512-FCPnDcILfpTE94u7BVlV4DmnSV5wE3+j25EEF+3dYPrVzkVCSoAHs318oWDGxnxsAgiL4HpL12Jc4XHmw9shpA==", + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.46.2.tgz", + "integrity": "sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.52.0.tgz", - "integrity": "sha512-br3DO7n4N8CXwTRbZS0MnB4WQ9YHfNjCwkCEzVR/wek/qNTDQKDb0nROmkFaNZ8ucUqUVKZi074dbwMwRDlK8Q==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.46.2.tgz", + "integrity": "sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.52.0.tgz", - "integrity": "sha512-b0T/Ca2c9KyEslKsVrGZvbe1UrrKKSdfXhBZ2pbpKahFUzJfziRZ0urbOm7V65O0tO/jwU+Lo/+bIiiyhzGt8w==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz", + "integrity": "sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.52.0.tgz", - "integrity": "sha512-ozBT8J/mtD4H4IAojw8QPirlcL2gHrI1BGuZ4/ZXXO/rTE1yQ4VIPJj4mTTbwo4FbkS1MoJsD/DsrqLzhnc4/g==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz", + "integrity": "sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.52.0.tgz", - "integrity": "sha512-gyyWcLD22tnabmoit4iukCXuoRc5HYJuUjPSEa8a0D/f/NlRafpWi52AlAaa4Uu/rsl7saHsJFTNjTptWbu2+A==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz", + "integrity": "sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.52.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" @@ -263,40 +263,40 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2200.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2200.0.tgz", - "integrity": "sha512-PAfnKRM2C7er2PwAkSLvkw/AtnMRTcmdG6pOrb3De++eVTuDeNCuYsIqrygvkFElrpsMHcnAAwTNtvyMds8b+w==", + "version": "0.2101.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.2.tgz", + "integrity": "sha512-pV2onJgp16xO0vAqEfRWVynRPPLVHydYLANNa3UX3l5T39JcYdMIoOHSIIl8tWrxVeOwiWd1ajub0VsFTUok4Q==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "22.0.0", + "@angular-devkit/core": "21.1.2", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/core": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-22.0.0.tgz", - "integrity": "sha512-GCEalkF17uygXnjHNyeIWuTzm16TDlhNLHsxbeYeJSJ48anwkZisL/L+oFzEmg8BGqx48nMGj2EVe4J8ADrSng==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.2.tgz", + "integrity": "sha512-0wl5nJlFWsbwfUB2CQeTSmnVQ8AtqqwM3bYPYtXSc+vA8+hzsOAjjDuRnBxZS9zTnqtXKXB1e7M3Iy7KUwh7LA==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.20.0", + "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.4", + "picomatch": "4.0.3", "rxjs": "7.8.2", "source-map": "0.7.6" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -310,82 +310,83 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-22.0.0.tgz", - "integrity": "sha512-Uefa/kgLD15B0wuxIFJuDvnVf2FuzXdnE/aMTd/fGor3otjrdegaU1tCeK9I0smHaiSWNvtLbhUbkNpuNG1cMw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.2.tgz", + "integrity": "sha512-PA3gkiFhHUuXd2XuP7yzKg/9N++bjw+uOl473KwIsMuZwMPhncKa4+mUYBaffDoPqaujZvjfo6mjtCBuiBv05w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "22.0.0", + "@angular-devkit/core": "21.1.2", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", - "ora": "9.4.0", + "ora": "9.0.0", "rxjs": "7.8.2" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular/build": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-22.0.0.tgz", - "integrity": "sha512-cMtzptD/ewrB435PREv434RXKPdDSfXmptfJTe7ik6Q6ixzmBEFpwmBsMNHQAJHwgV5fCrmLuWVaaq0HY0MpxA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.2.tgz", + "integrity": "sha512-5hl7OTZeQcdkr/3LXSijLuUCwlcqGyYJYOb8GbFqSifSR03JFI3tLQtyQ0LX2CXv3MOx1qFUQbYVfcjW5M36QQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2200.0", - "@babel/core": "7.29.0", + "@angular-devkit/architect": "0.2101.2", + "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "6.0.12", - "@vitejs/plugin-basic-ssl": "2.3.0", - "beasties": "0.4.2", + "@inquirer/confirm": "5.1.21", + "@vitejs/plugin-basic-ssl": "2.1.0", + "beasties": "0.3.5", "browserslist": "^4.26.0", - "esbuild": "0.28.0", - "https-proxy-agent": "9.0.0", + "esbuild": "0.27.2", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", - "listr2": "10.2.1", + "listr2": "9.0.5", "magic-string": "0.30.21", "mrmime": "2.0.1", - "parse5-html-rewriting-stream": "8.0.1", - "picomatch": "4.0.4", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.3", "piscina": "5.1.4", - "rollup": "4.60.2", - "sass": "1.99.0", - "semver": "7.7.4", + "rolldown": "1.0.0-beta.58", + "sass": "1.97.1", + "semver": "7.7.3", "source-map-support": "0.5.21", - "tinyglobby": "0.2.16", - "vite": "7.3.2", - "watchpack": "2.5.1" + "tinyglobby": "0.2.15", + "undici": "7.18.2", + "vite": "7.3.0", + "watchpack": "2.5.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "lmdb": "3.5.4" + "lmdb": "3.4.4" }, "peerDependencies": { - "@angular/compiler": "^22.0.0", - "@angular/compiler-cli": "^22.0.0", - "@angular/core": "^22.0.0", - "@angular/localize": "^22.0.0", - "@angular/platform-browser": "^22.0.0", - "@angular/platform-server": "^22.0.0", - "@angular/service-worker": "^22.0.0", - "@angular/ssr": "^22.0.0", - "istanbul-lib-instrument": "^6.0.0", + "@angular/compiler": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/service-worker": "^21.0.0", + "@angular/ssr": "^21.1.2", "karma": "^6.4.0", "less": "^4.2.0", - "ng-packagr": "^22.0.0", + "ng-packagr": "^21.0.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=6.0 <6.1", + "typescript": ">=5.9 <6.0", "vitest": "^4.0.8" }, "peerDependenciesMeta": { @@ -407,9 +408,6 @@ "@angular/ssr": { "optional": true }, - "istanbul-lib-instrument": { - "optional": true - }, "karma": { "optional": true }, @@ -430,532 +428,167 @@ } } }, - "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], + "node_modules/@angular/build/node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/build/node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], + "node_modules/@angular/build/node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/build/node_modules/@types/node": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], + "node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], + "node_modules/@angular/build/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@angular/build/node_modules/@inquirer/confirm": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.12.tgz", - "integrity": "sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==", + "node_modules/@angular/build/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.9", - "@inquirer/type": "^4.0.5" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" + "node": ">=8" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@angular/build/node_modules/@inquirer/core": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", - "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", + "node_modules/@angular/build/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7", - "cli-width": "^4.1.0", - "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0" - }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@angular/build/node_modules/@inquirer/type": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", - "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", + "node_modules/@angular/build/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" - }, - "peerDependencies": { - "@types/node": ">=18" + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", - "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", + "node_modules/@angular/build/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">=8" } }, "node_modules/@angular/build/node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", "dependencies": { @@ -1027,113 +660,88 @@ } } }, - "node_modules/@angular/build/node_modules/vite/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "node_modules/@angular/build/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "node": ">=8" } }, "node_modules/@angular/cdk": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-22.0.0.tgz", - "integrity": "sha512-mahXlRD4V8Tj2NtttRNFfuTru5HmMgJt8ny/SJ/Bx1NCOymxLEqxREACNpuwMf/3q1XUe33/oh++mvJQ2JYkgw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.2.tgz", + "integrity": "sha512-0q+PhBKmjKO0Yi353VCpMxT0g787cllLhdpyxh00i3twxNWvFkQZgy2Ih187ZXydvW+u9mFkK9+UGLzncQ0yng==", "license": "MIT", "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": "^22.0.0 || ^23.0.0", - "@angular/core": "^22.0.0 || ^23.0.0", - "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/platform-browser": "^21.0.0 || ^22.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/cli": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-22.0.0.tgz", - "integrity": "sha512-VJv4ryJ2yLy79FqAq6WzZCLU3U5WU3n5NS7av5LbatxxOb07Jg80J/DBPSeA3rJ5EzpSIrj8mHLvW8Eunn58Eg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.2.tgz", + "integrity": "sha512-AHjXCBl2PEilMJct6DX3ih5Fl5PiKpNDIj0ViTyVh1YcfpYjt6NzhVlV2o++8VNPNH/vMcmf2551LZIDProXXA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2200.0", - "@angular-devkit/core": "22.0.0", - "@angular-devkit/schematics": "22.0.0", - "@inquirer/prompts": "8.4.2", - "@listr2/prompt-adapter-inquirer": "4.2.3", - "@modelcontextprotocol/sdk": "1.29.0", - "@schematics/angular": "22.0.0", + "@angular-devkit/architect": "0.2101.2", + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", + "@inquirer/prompts": "7.10.1", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.25.2", + "@schematics/angular": "21.1.2", "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.52.0", + "algoliasearch": "5.46.2", "ini": "6.0.0", "jsonc-parser": "3.3.1", - "listr2": "10.2.1", + "listr2": "9.0.5", "npm-package-arg": "13.0.2", - "pacote": "21.5.0", - "parse5-html-rewriting-stream": "8.0.1", - "semver": "7.7.4", + "pacote": "21.0.4", + "parse5-html-rewriting-stream": "8.0.0", + "resolve": "1.22.11", + "semver": "7.7.3", "yargs": "18.0.0", - "zod": "4.4.2" + "zod": "4.3.5" }, "bin": { "ng": "bin/ng.js" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular/cli/node_modules/@inquirer/checkbox": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", - "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/core": "^11.2.1", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1145,17 +753,17 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/confirm": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", - "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1167,22 +775,23 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/core": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", - "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7", + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", - "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0" + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1194,18 +803,18 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/editor": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", - "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/external-editor": "^3.0.3", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1217,17 +826,18 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/expand": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", - "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1239,17 +849,17 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, "license": "MIT", "dependencies": { "chardet": "^2.1.1", - "iconv-lite": "^0.7.2" + "iconv-lite": "^0.7.0" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1261,17 +871,17 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/input": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", - "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1283,17 +893,17 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/number": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", - "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1305,18 +915,18 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/password": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", - "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1328,25 +938,25 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/prompts": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.4.2.tgz", - "integrity": "sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.1.4", - "@inquirer/confirm": "^6.0.12", - "@inquirer/editor": "^5.1.1", - "@inquirer/expand": "^5.0.13", - "@inquirer/input": "^5.0.12", - "@inquirer/number": "^4.0.12", - "@inquirer/password": "^5.0.12", - "@inquirer/rawlist": "^5.2.8", - "@inquirer/search": "^4.1.8", - "@inquirer/select": "^5.1.4" + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1358,17 +968,18 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/rawlist": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", - "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1380,18 +991,19 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/search": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", - "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^11.2.1", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7" + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1403,19 +1015,20 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/select": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", - "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.7", - "@inquirer/core": "^11.2.1", - "@inquirer/figures": "^2.0.7", - "@inquirer/type": "^4.0.7" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1427,13 +1040,13 @@ } }, "node_modules/@angular/cli/node_modules/@inquirer/type": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", - "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" }, "peerDependencies": { "@types/node": ">=18" @@ -1445,57 +1058,148 @@ } }, "node_modules/@angular/cli/node_modules/@listr2/prompt-adapter-inquirer": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-4.2.3.tgz", - "integrity": "sha512-Co9U3AJ3LW0J8XBHjVoNKA79dMAyFt8EZH3OaKTMcDTj8r+6kG3vSUPq/eGLHT7P0iK3uLaFfhdFYd3033P24g==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", + "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^4.0.5" + "@inquirer/type": "^3.0.8" }, "engines": { - "node": ">=22.13.0" + "node": ">=20.0.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 9", - "listr2": "10.2.1" + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.5" + } + }, + "node_modules/@angular/cli/node_modules/@types/node": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@angular/cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular/cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular/cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular/cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/@angular/common": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-22.0.0.tgz", - "integrity": "sha512-O9Qk60/OQQuZXMeXRfOpsq+/B609nd5KIxjSZFddRQUfSMZrdvVDNK0irjgYVKGDkMx3dqCiQ8a4nAIdGy7V6A==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.2.tgz", + "integrity": "sha512-NK26OG1+/3EXLDWstSPmdGbkpt8bP9AsT9J7EBornMswUjmQDbjyb85N/esKjRjDMkw4p/aKpBo24eCV5uUmBA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "22.0.0", + "@angular/core": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-22.0.0.tgz", - "integrity": "sha512-g8Ab5Lcji2cxADfcPPM7kltEzSlCjUevPK3udm+3S5uhkTcLNH236/XCAwhD1XIgHQDv9p7FWm1xS7zkvbwXhA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.2.tgz", + "integrity": "sha512-5OFdZPNix7iK4HSdRxPgg74VvcmQZAMzv9ACYZ8iGfNxiJUjFSurfz0AtVEh0oE2oZDH1v48bHI1s+0ljCHZhA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@angular/compiler-cli": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-22.0.0.tgz", - "integrity": "sha512-7r4ufQ8CUhlRBol/N8a6psg40kOu/Y3H6iuUGwq9cs6Gs/fII7mVB6QgPi0bCiNDjaQB7xGq6NZ0iT6CPBH8Sw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.2.tgz", + "integrity": "sha512-h+sX7QvSz58KvmRwNMa33EZHti8Cnw1DL01kInJ/foDchC/O2VMOumeGHS+lAe48t2Nbhiq/obgf275TkDZYsA==", "license": "MIT", "dependencies": { - "@babel/core": "7.29.0", + "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^5.0.0", "convert-source-map": "^1.5.1", @@ -1509,11 +1213,11 @@ "ngc": "bundles/src/bin/ngc.js" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "22.0.0", - "typescript": ">=6.0 <6.1" + "@angular/compiler": "21.1.2", + "typescript": ">=5.9 <6.0" }, "peerDependenciesMeta": { "typescript": { @@ -1522,18 +1226,18 @@ } }, "node_modules/@angular/core": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-22.0.0.tgz", - "integrity": "sha512-H4lzunB+LUNylQ3hZGYWDz1NfNAdFzPdOadwuS6VpPyxF4Ti0MLyAfx7NDnyTrmdY2/PFx8I6jXrveNlIsORXg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.2.tgz", + "integrity": "sha512-W2xxRb7noOD1DdMwKaZ3chFhii6nutaNIXt7dfWsMWoujg3Kqpdn1ukeyW5aHKQZvCJTIGr4f3whZ8Sj/17aCA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "22.0.0", + "@angular/compiler": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, @@ -1547,32 +1251,31 @@ } }, "node_modules/@angular/forms": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-22.0.0.tgz", - "integrity": "sha512-OjyiF0hgbNXrFbIgqazyNJlFTtqfU0kfwJgmlMr4FG+e9P89UmgZhELUWs1CIuNX+jhh3DePm+Fo26dJIS7cfg==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.2.tgz", + "integrity": "sha512-dY56FuoBEvfLMtatKGg1vMFSwgySzWJm3URaBj3GpFTjhnuByHoxH4Lb5u50lrrVc9VQt/BZmq3mDZXjlx6Qgw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", - "tslib": "^2.3.0", - "zod": "^4.0.10" + "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "22.0.0", - "@angular/core": "22.0.0", - "@angular/platform-browser": "22.0.0", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2", + "@angular/platform-browser": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/localize": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-22.0.0.tgz", - "integrity": "sha512-K3qGoi3+jGHp+HN2YGMlCaQHDYfF8O+XxvOvAffl1TpoGfzFqEwDv3tHpYmrF99JH5S8NiTBWcdDBZ/BP6E9qQ==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-21.1.2.tgz", + "integrity": "sha512-y4vV8Lc39DC4qT3vI9RqpUYQ6Iu2WC/6pxt3o3YYJRk6eUyLSGpcp/jbfPjqDlZQNfKLdTDHEC7HeQnwD69/Mw==", "license": "MIT", "dependencies": { - "@babel/core": "7.29.0", + "@babel/core": "7.28.5", "@types/babel__core": "7.20.5", "tinyglobby": "^0.2.12", "yargs": "^18.0.0" @@ -1583,59 +1286,59 @@ "localize-translate": "tools/bundles/src/translate/cli.js" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "22.0.0", - "@angular/compiler-cli": "22.0.0" + "@angular/compiler": "21.1.2", + "@angular/compiler-cli": "21.1.2" } }, "node_modules/@angular/material": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-22.0.0.tgz", - "integrity": "sha512-sRxbEEgmaVqbwcT65PWfZV/cIpLsZ8vD+yc6rH83L83jDJWVABpqDWFg8Hl88iFMOts8iffml6GddvXhsNHAEQ==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-21.1.2.tgz", + "integrity": "sha512-LAnnbeuyLu8Foe3VRIM23KKi7mY/Jv0AE1Y0hHQa8RLluY0fHiQrtcPCdv96K8sn2nQmy0f4v03JERi6r4nDJw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/cdk": "22.0.0", - "@angular/common": "^22.0.0 || ^23.0.0", - "@angular/core": "^22.0.0 || ^23.0.0", - "@angular/forms": "^22.0.0 || ^23.0.0", - "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "@angular/cdk": "21.1.2", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/forms": "^21.0.0 || ^22.0.0", + "@angular/platform-browser": "^21.0.0 || ^22.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material-luxon-adapter": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-22.0.0.tgz", - "integrity": "sha512-47b3kOvR0AyVi6St+1f9fOu20hDDGEP6Jlxi7sR9eaay6sKeSxD8TE1eXZRQNmsnpVvfDnMDJ/PgoPY6vTPkAA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-21.1.2.tgz", + "integrity": "sha512-A5/rNZTNmTYCZxgGqTee284SICHHy5wTmOh6yfqG/gK37T/K+iJ42oGqS2zvphAQGSgQhwxnQe+jJOxk9PHDGQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/core": "^22.0.0 || ^23.0.0", - "@angular/material": "22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/material": "21.1.2", "luxon": "^3.7.2" } }, "node_modules/@angular/platform-browser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-22.0.0.tgz", - "integrity": "sha512-ry4Hdov19V8sA+MrIEIeISXA8GKWluCDUg06PaAm9nJveYjQUUlElZqa3fTNGOmy3/eNV8H9nmaroD27L8yU1A==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.2.tgz", + "integrity": "sha512-8vnCbQhxugQ3meGQ0YlSp0uNBYUjpFXYjFnGQ0Xq5jvzc9WX7KSix6+AydEjZtQfc1bWRetBTOlhQpqnwYp53g==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "22.0.0", - "@angular/common": "22.0.0", - "@angular/core": "22.0.0" + "@angular/animations": "21.1.2", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1644,27 +1347,27 @@ } }, "node_modules/@angular/router": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-22.0.0.tgz", - "integrity": "sha512-CCtonkDVkkfKLtuKol8rC1zmWI4QX7w3uUtdlOoz6K9HXAhpZYGcSq5RyloA767QLj36u7108K9xHBs2abOajQ==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.2.tgz", + "integrity": "sha512-APl4tkTJIrpejlULLrGtIdLuJkNctPy0pnVijrJLR52nEV0xX165ulXk3XrL9QnMk0iy950aTYtoTal4aMw16Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "22.0.0", - "@angular/core": "22.0.0", - "@angular/platform-browser": "22.0.0", + "@angular/common": "21.1.2", + "@angular/core": "21.1.2", + "@angular/platform-browser": "21.1.2", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -1676,29 +1379,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -1730,13 +1433,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1876,12 +1579,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -1905,17 +1608,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", + "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -1923,9 +1626,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1945,10 +1648,44 @@ "node": ">=0.1.90" } }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -1963,9 +1700,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -1980,9 +1717,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -1997,9 +1734,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -2014,9 +1751,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -2031,9 +1768,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -2048,9 +1785,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -2065,9 +1802,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -2082,9 +1819,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -2099,9 +1836,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -2116,9 +1853,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -2133,9 +1870,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -2150,9 +1887,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -2167,9 +1904,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -2184,9 +1921,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -2201,9 +1938,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -2218,9 +1955,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -2235,9 +1972,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -2252,9 +1989,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -2269,9 +2006,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -2286,9 +2023,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -2303,9 +2040,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -2320,9 +2057,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -2337,9 +2074,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -2354,9 +2091,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -2371,9 +2108,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -2387,28 +2124,10 @@ "node": ">=18" } }, - "node_modules/@gar/promise-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@harperfast/extended-iterable": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", - "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "dev": true, "license": "MIT", "engines": { @@ -2419,23 +2138,46 @@ } }, "node_modules/@inquirer/ansi": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", - "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" } }, "node_modules/@inquirer/figures": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", - "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + "node": ">=18" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/@isaacs/fs-minipass": { @@ -2507,9 +2249,9 @@ } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.4.tgz", - "integrity": "sha512-Kk4Kz3iyu1QiLsLZBS9Af1eSKUC8VR2T+/jyE2iAyuGw2VwK08pp5iTbZnXn6sWu0LogO/RFktMxOjiDA2sS3w==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.4.tgz", + "integrity": "sha512-XaKL705gDWd6XVls3ATDj13ZdML/LqSIxwgnYpG8xTzH2ifArx8fMMDdvqGE/Emd+W6R90W2fveZcJ0AyS8Y0w==", "cpu": [ "arm64" ], @@ -2521,9 +2263,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.4.tgz", - "integrity": "sha512-BEe5Rp3trn26oxoXOVL5HVDoiYmjUDwr8NRPkBOdUdCSBEorKI+7JrZLRKAdxO+G6cGQLgseXk0gR7qIQa7aGw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.4.tgz", + "integrity": "sha512-GPHGEVcwJlkD01GmIr7B4kvbIcUDS2+kBadVEd7lU4can1RZaZQLDDBJRrrNfS2Kavvl0VLI/cMv7UASAXGrww==", "cpu": [ "x64" ], @@ -2535,9 +2277,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.4.tgz", - "integrity": "sha512-SGbFR7816uBcTHc2ZY4S6WyOkl9bICnzqTQd2Mv4V/j24cfds88xx2nC6cm/y8zGQL7Ds31YF/5NGxjgcdM5Hw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.4.tgz", + "integrity": "sha512-cmev5/dZr5ACKri9f6GU6lZCXTjMhV72xujlbOhFCgFXrt4W0TxGsmY8kA1BITvH60JBKE50cSxsiulybAbrrw==", "cpu": [ "arm" ], @@ -2549,9 +2291,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.4.tgz", - "integrity": "sha512-cUXEengO8o60v1SWerJTH4/RH4U3+9jC0/4njp2Z9NdmvaGzhKsbRM2wpXuRYrN8tytsoJCg0SvWEWwHAwLbCA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.4.tgz", + "integrity": "sha512-mALqr7DE42HsiwVTKpQWxacjHoJk+e9p00RWIJqTACh/hpucxp/0lK/XMh5XzWnU/TDCZLukq1+vNqnNumTP/Q==", "cpu": [ "arm64" ], @@ -2563,9 +2305,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.4.tgz", - "integrity": "sha512-Gxq8jpgOWXwd0PUR+c9R2Ik1/uBnGd5GMIIzRRDqABCkvmjtC3KWcyhesV9jSPCz759isl0NlbsstZ2oyvk8lA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.4.tgz", + "integrity": "sha512-QjLs8OcmCNcraAcLoZyFlo0atzBJniQLLwhtR+ymQqS5kLYpV5RqwriL87BW+ZiR9ZiGgZx3evrz5vnWPtJ1fQ==", "cpu": [ "x64" ], @@ -2577,9 +2319,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.4.tgz", - "integrity": "sha512-pKv1DJ1bPZAaHkdFsSz5IDfUG8x9vntgquXF9/Dm2xuupcIe/EkLzylpoBxppFVK5vzbV561Dq26jNY2fIMA7g==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.4.tgz", + "integrity": "sha512-tr/pwHDlZ33forLGAr0tI04cRmP4SgF93yHbb+2zvZiDEyln5yMHhbKDySxY66aUOkhvBvTuHq9q/3YmTj6ZHQ==", "cpu": [ "arm64" ], @@ -2591,9 +2333,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.4.tgz", - "integrity": "sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.4.tgz", + "integrity": "sha512-KRzfocJzB/mgoTCqnMawuLSKheHRVTqWfSmouIgYpFs6Hx4zvZSvsZKSCEb5gHmICy7qsx9l06jk3MFTtiFVAQ==", "cpu": [ "x64" ], @@ -2605,13 +2347,13 @@ ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", "dev": true, "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -2619,15 +2361,14 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" @@ -2646,9 +2387,9 @@ } }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", - "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", "cpu": [ "arm64" ], @@ -2660,9 +2401,9 @@ ] }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", - "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", "cpu": [ "x64" ], @@ -2674,9 +2415,9 @@ ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", - "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", "cpu": [ "arm" ], @@ -2688,9 +2429,9 @@ ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", - "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", "cpu": [ "arm64" ], @@ -2702,9 +2443,9 @@ ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", - "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", "cpu": [ "x64" ], @@ -2716,9 +2457,9 @@ ] }, "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", - "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", "cpu": [ "x64" ], @@ -3052,51 +2793,44 @@ "node": ">= 10" } }, - "node_modules/@npmcli/agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.2.tgz", - "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": ">= 14" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3117,18 +2851,18 @@ } }, "node_modules/@npmcli/git": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", - "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "dev": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^6.0.0" }, @@ -3137,19 +2871,19 @@ } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=20" + "node": ">=16" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3157,13 +2891,13 @@ } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" @@ -3200,9 +2934,9 @@ } }, "node_modules/@npmcli/package-json": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", - "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3212,64 +2946,41 @@ "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" + "validate-npm-package-license": "^3.0.4" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3289,23 +3000,23 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=20" + "node": ">=16" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" @@ -3325,9 +3036,9 @@ } }, "node_modules/@npmcli/run-script": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", - "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -3335,12 +3046,49 @@ "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" + "proc-log": "^6.0.0", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@oxc-project/types": { + "version": "0.106.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.106.0.tgz", + "integrity": "sha512-QdsH3rZq480VnOHSHgPYOhjL8O8LBdcnSjM408BpPCCUc0JYYZPG9Gafl9i3OcGk/7137o+gweb4cCv3WAUykg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -3470,9 +3218,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3494,9 +3239,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3518,9 +3260,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3542,9 +3281,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3566,9 +3302,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3590,9 +3323,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3606,10 +3336,234 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-mWj5eE4Qc8TbPdGGaaLvBb9XfDPvE1EmZkJQgiGKwchkWH4oAJcRAKMTw7ZHnb1L+t7Ah41sBkAecaIsuUgsug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-wFxUymI/5R8bH8qZFYDfAxAN9CyISEIYke+95oZPiv6EWo88aa5rskjVcCpKA532R+klFmdqjbbaD56GNmTF4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.58.tgz", + "integrity": "sha512-ybp3MkPj23VDV9PhtRwdU5qrGhlViWRV5BjKwO6epaSlUD5lW0WyY+roN3ZAzbma/9RrMTgZ/a/gtQq8YXOcqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.58.tgz", + "integrity": "sha512-Evxj3yh7FWvyklUYZa0qTVT9N2zX9TPDqGF056hl8hlCZ9/ndQ2xMv6uw9PD1VlLpukbsqL+/C6M0qwipL0QMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.58.tgz", + "integrity": "sha512-tYeXprDOrEgVHUbPXH6MPso4cM/c6RTkmJNICMQlYdki4hGMh92aj3yU6CKs+4X5gfG0yj5kVUw/L4M685SYag==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.58.tgz", + "integrity": "sha512-N78vmZzP6zG967Ohr+MasCjmKtis0geZ1SOVmxrA0/bklTQSzH5kHEjW5Qn+i1taFno6GEre1E40v0wuWsNOQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.58.tgz", + "integrity": "sha512-l+p4QVtG72C7wI2SIkNQw/KQtSjuYwS3rV6AKcWrRBF62ClsFUcif5vLaZIEbPrCXu5OFRXigXFJnxYsVVZqdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.58.tgz", + "integrity": "sha512-urzJX0HrXxIh0FfxwWRjfPCMeInU9qsImLQxHBgLp5ivji1EEUnOfux8KxPPnRQthJyneBrN2LeqUix9DYrNaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.58.tgz", + "integrity": "sha512-7ijfVK3GISnXIwq/1FZo+KyAUJjL3kWPJ7rViAL6MWeEBhEgRzJ0yEd9I8N9aut8Y8ab+EKFJyRNMWZuUBwQ0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.58.tgz", + "integrity": "sha512-/m7sKZCS+cUULbzyJTIlv8JbjNohxbpAOA6cM+lgWgqVzPee3U6jpwydrib328JFN/gF9A99IZEnuGYqEDJdww==", "cpu": [ "arm64" ], @@ -3617,22 +3571,35 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "openharmony" ], "engines": { - "node": ">= 10.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.58.tgz", + "integrity": "sha512-6SZk7zMgv+y3wFFQ9qE5P9NnRHcRsptL1ypmudD26PDY+PvFCvfHRkJNfclWnvacVGxjowr7JOL3a9fd1wWhUw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.58.tgz", + "integrity": "sha512-sFqfYPnBZ6xBhMkadB7UD0yjEDRvs7ipR3nCggblN+N4ODCXY6qhg/bKL39+W+dgQybL7ErD4EGERVbW9DAWvg==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", @@ -3641,17 +3608,13 @@ "win32" ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.58.tgz", + "integrity": "sha512-AnFWJdAqB8+IDPcGrATYs67Kik/6tnndNJV2jGRmwlbeNiQQ8GhRJU8ETRlINfII0pqi9k4WWLnb00p1QCxw/Q==", "cpu": [ "x64" ], @@ -3662,25 +3625,20 @@ "win32" ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@parcel/watcher/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.58.tgz", + "integrity": "sha512-qWhDs6yFGR5xDfdrwiSa3CWGIHxD597uGE/A9xGqytBjANvh4rLCTTkq7szhMV4+Ygh+PMS90KVJ8xWG/TkX4w==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", - "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", "cpu": [ "arm" ], @@ -3692,9 +3650,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", - "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", "cpu": [ "arm64" ], @@ -3706,9 +3664,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", - "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", "cpu": [ "arm64" ], @@ -3720,9 +3678,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", - "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", "cpu": [ "x64" ], @@ -3734,9 +3692,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", - "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", "cpu": [ "arm64" ], @@ -3748,9 +3706,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", - "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", "cpu": [ "x64" ], @@ -3762,16 +3720,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", - "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3779,16 +3734,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", - "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3796,16 +3748,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", - "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3813,16 +3762,13 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", - "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3830,16 +3776,13 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", - "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", "cpu": [ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3847,16 +3790,13 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", - "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", "cpu": [ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3864,16 +3804,13 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", - "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3881,16 +3818,13 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", - "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3898,16 +3832,13 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", - "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3915,16 +3846,13 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", - "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3932,16 +3860,13 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", - "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3949,16 +3874,13 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", - "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3966,16 +3888,13 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", - "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3983,9 +3902,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", - "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", "cpu": [ "x64" ], @@ -3997,9 +3916,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", - "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", "cpu": [ "arm64" ], @@ -4011,9 +3930,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", - "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", "cpu": [ "arm64" ], @@ -4025,9 +3944,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", - "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", "cpu": [ "ia32" ], @@ -4039,9 +3958,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", - "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", "cpu": [ "x64" ], @@ -4053,9 +3972,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", - "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", "cpu": [ "x64" ], @@ -4067,19 +3986,18 @@ ] }, "node_modules/@schematics/angular": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-22.0.0.tgz", - "integrity": "sha512-LqjnpBD0knsZulOCiaBxb9vDUYq6RHyF2VMlQI1gkgJaDAd2YcvK3/H2Xy9tEH1oA1ftbo2p0kpzNtJzSwtBcA==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.2.tgz", + "integrity": "sha512-kxwxhCIUrj7DfzEtDSs/pi/w+aII/WQLpPfLgoQCWE8/95v60WnTfd1afmsXsFoxikKPxkwoPWtU2YbhSoX9MQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "22.0.0", - "@angular-devkit/schematics": "22.0.0", - "jsonc-parser": "3.3.1", - "typescript": "6.0.3" + "@angular-devkit/core": "21.1.2", + "@angular-devkit/schematics": "21.1.2", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } @@ -4098,9 +4016,9 @@ } }, "node_modules/@sigstore/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.1.tgz", - "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", + "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4108,9 +4026,9 @@ } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", - "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4118,27 +4036,27 @@ } }, "node_modules/@sigstore/sign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", - "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", + "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.4", - "proc-log": "^6.1.0" + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", - "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", + "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4150,14 +4068,14 @@ } }, "node_modules/@sigstore/verify": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.1.tgz", - "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", "dev": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { @@ -4201,45 +4119,33 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@tufjs/models/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4319,16 +4225,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -4361,19 +4257,19 @@ } }, "node_modules/agent-base": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", - "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20" + "node": ">= 14" } }, "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { @@ -4406,35 +4302,35 @@ } }, "node_modules/algoliasearch": { - "version": "5.52.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.52.0.tgz", - "integrity": "sha512-0ZzY9mjqV7gop/AH8pIBiAS8giXP7WcSiUfoFYIzYAK9QC5c37E4SIVtJVBMwlURc0/uNt2o4RcNRvdHa4CJ5w==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.2.tgz", + "integrity": "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/abtesting": "1.18.0", - "@algolia/client-abtesting": "5.52.0", - "@algolia/client-analytics": "5.52.0", - "@algolia/client-common": "5.52.0", - "@algolia/client-insights": "5.52.0", - "@algolia/client-personalization": "5.52.0", - "@algolia/client-query-suggestions": "5.52.0", - "@algolia/client-search": "5.52.0", - "@algolia/ingestion": "1.52.0", - "@algolia/monitoring": "1.52.0", - "@algolia/recommend": "5.52.0", - "@algolia/requester-browser-xhr": "5.52.0", - "@algolia/requester-fetch": "5.52.0", - "@algolia/requester-node-http": "5.52.0" + "@algolia/abtesting": "1.12.2", + "@algolia/client-abtesting": "5.46.2", + "@algolia/client-analytics": "5.46.2", + "@algolia/client-common": "5.46.2", + "@algolia/client-insights": "5.46.2", + "@algolia/client-personalization": "5.46.2", + "@algolia/client-query-suggestions": "5.46.2", + "@algolia/client-search": "5.46.2", + "@algolia/ingestion": "1.46.2", + "@algolia/monitoring": "1.46.2", + "@algolia/recommend": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, "license": "MIT", "dependencies": { @@ -4486,9 +4382,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -4516,21 +4412,18 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/beasties": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.4.2.tgz", - "integrity": "sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4541,11 +4434,10 @@ "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", - "postcss-media-query-parser": "^0.2.3", - "postcss-safe-parser": "^7.0.1" + "postcss-media-query-parser": "^0.2.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, "node_modules/binary-extensions": { @@ -4594,9 +4486,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -4668,9 +4560,9 @@ } }, "node_modules/cacache": { - "version": "20.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "dev": true, "license": "ISC", "dependencies": { @@ -4683,57 +4575,35 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^13.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/cacache/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/cacache/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -4741,16 +4611,16 @@ } }, "node_modules/cacache/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4788,9 +4658,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "funding": [ { "type": "opencollective", @@ -4882,14 +4752,14 @@ } }, "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "engines": { "node": ">=20" @@ -4965,6 +4835,13 @@ "dev": true, "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5058,9 +4935,9 @@ } }, "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "dev": true, "license": "MIT", "engines": { @@ -5338,9 +5215,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.302", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", - "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "version": "1.5.279", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", + "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -5360,23 +5237,47 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/engine.io": { - "version": "6.6.8", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", - "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", "dev": true, "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", - "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.20.1" + "ws": "~8.18.3" }, "engines": { "node": ">=10.2.0" @@ -5491,6 +5392,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -5525,9 +5433,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5538,32 +5446,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -5613,9 +5521,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "dev": true, "license": "MIT", "engines": { @@ -5674,14 +5582,11 @@ } }, "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "dev": true, "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, "engines": { "node": ">= 16" }, @@ -5706,27 +5611,10 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -5740,16 +5628,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", - "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5803,16 +5681,16 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -5929,9 +5807,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "license": "MIT", "engines": { "node": ">=18" @@ -5983,7 +5861,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -6094,19 +5972,20 @@ } }, "node_modules/hono": { - "version": "4.12.23", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", - "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } }, "node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "license": "ISC", "dependencies": { @@ -6117,9 +5996,9 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -6223,28 +6102,18 @@ "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/https-proxy-agent": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.0.0.tgz", - "integrity": "sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "9.0.0", - "debug": "^4.3.4" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 20" + "node": ">= 14" } }, "node_modules/iconv-lite": { @@ -6277,52 +6146,39 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/ignore-walk/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/immutable": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.6.tgz", - "integrity": "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "dev": true, "license": "MIT" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6353,9 +6209,9 @@ } }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -6385,6 +6241,22 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6516,6 +6388,23 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -6578,9 +6467,9 @@ "license": "MIT" }, "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", "dev": true, "license": "MIT", "funding": { @@ -6837,9 +6726,9 @@ } }, "node_modules/karma/node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { @@ -6851,7 +6740,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.15.1", + "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -6972,9 +6861,9 @@ "license": "MIT" }, "node_modules/karma/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -7113,57 +7002,39 @@ } }, "node_modules/listr2": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", - "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^5.2.0", - "eventemitter3": "^5.0.4", + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", - "wrap-ansi": "^10.0.0" - }, - "engines": { - "node": ">=22.13.0" - } - }, - "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" + "wrap-ansi": "^9.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "engines": { + "node": ">=20.0.0" } }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, "node_modules/lmdb": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.5.4.tgz", - "integrity": "sha512-9FKQA6G1MMtqNxfxvSBNXD/axeG2QRjYbNh0/ykRL5xYcRbCm2vXq7B9bhc7nSuKdHzr8/BHIwfPuYYH1UsXXw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.4.tgz", + "integrity": "sha512-+Y2DqovevLkb6DrSQ6SXTYLEd6kvlRbhsxzgJrk7BUfOVA/mt21ak6pFDZDKxiAczHMWxrb02kXBTSTIA0O94A==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "@harperfast/extended-iterable": "^1.0.3", "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", @@ -7174,19 +7045,19 @@ "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.5.4", - "@lmdb/lmdb-darwin-x64": "3.5.4", - "@lmdb/lmdb-linux-arm": "3.5.4", - "@lmdb/lmdb-linux-arm64": "3.5.4", - "@lmdb/lmdb-linux-x64": "3.5.4", - "@lmdb/lmdb-win32-arm64": "3.5.4", - "@lmdb/lmdb-win32-x64": "3.5.4" + "@lmdb/lmdb-darwin-arm64": "3.4.4", + "@lmdb/lmdb-darwin-x64": "3.4.4", + "@lmdb/lmdb-linux-arm": "3.4.4", + "@lmdb/lmdb-linux-arm64": "3.4.4", + "@lmdb/lmdb-linux-x64": "3.4.4", + "@lmdb/lmdb-win32-arm64": "3.4.4", + "@lmdb/lmdb-win32-x64": "3.4.4" } }, "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -7227,23 +7098,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/log4js": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", @@ -7306,15 +7160,13 @@ } }, "node_modules/make-fetch-happen": { - "version": "15.0.6", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", - "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "dev": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", @@ -7323,6 +7175,7 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { @@ -7416,9 +7269,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -7439,11 +7292,11 @@ } }, "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -7462,29 +7315,29 @@ } }, "node_modules/minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", + "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "iconv-lite": "^0.7.2" + "encoding": "^0.1.13" } }, "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -7546,18 +7399,38 @@ "license": "ISC" }, "node_modules/minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.1.2" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", @@ -7601,9 +7474,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.12", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.12.tgz", - "integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==", + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", + "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", "dev": true, "license": "MIT", "optional": true, @@ -7612,9 +7485,9 @@ } }, "node_modules/msgpackr-extract": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", - "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7626,28 +7499,28 @@ "download-msgpackr-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, "node_modules/mute-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", - "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -7682,21 +7555,21 @@ "optional": true }, "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", - "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { @@ -7723,23 +7596,23 @@ } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=20" + "node": ">=16" } }, "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" @@ -7833,9 +7706,9 @@ } }, "node_modules/npm-packlist": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", - "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", + "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", "dev": true, "license": "ISC", "dependencies": { @@ -7958,9 +7831,9 @@ } }, "node_modules/ora": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.0.tgz", - "integrity": "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", + "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", "dev": true, "license": "MIT", "dependencies": { @@ -7970,8 +7843,9 @@ "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.2", - "string-width": "^8.1.0" + "stdin-discarder": "^0.2.2", + "string-width": "^8.1.0", + "strip-ansi": "^7.1.2" }, "engines": { "node": ">=20" @@ -8002,13 +7876,12 @@ } }, "node_modules/pacote": { - "version": "21.5.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.0.tgz", - "integrity": "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==", + "version": "21.0.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.4.tgz", + "integrity": "sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==", "dev": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", @@ -8022,6 +7895,7 @@ "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" @@ -8046,13 +7920,13 @@ } }, "node_modules/parse5-html-rewriting-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.1.tgz", - "integrity": "sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^8.0.0", + "entities": "^6.0.0", "parse5": "^8.0.0", "parse5-sax-parser": "^8.0.0" }, @@ -8061,13 +7935,13 @@ } }, "node_modules/parse5-html-rewriting-stream/node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=20.19.0" + "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -8128,10 +8002,17 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -8139,16 +8020,16 @@ "minipass": "^7.1.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -8156,9 +8037,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "dev": true, "license": "MIT", "funding": { @@ -8173,9 +8054,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -8208,9 +8089,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -8228,7 +8109,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8243,33 +8124,6 @@ "dev": true, "license": "MIT" }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -8280,6 +8134,20 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8312,9 +8180,9 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8399,6 +8267,27 @@ "dev": true, "license": "MIT" }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -8416,6 +8305,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -8440,10 +8339,42 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rolldown": { + "version": "1.0.0-beta.58", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.58.tgz", + "integrity": "sha512-v1FCjMZCan7f+xGAHBi+mqiE4MlH7I+SXEHSQSJoMOGNNB2UYtvMiejsq9YuUOiZjNeUeV/a21nSFbrUR+4ZCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.106.0", + "@rolldown/pluginutils": "1.0.0-beta.58" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.58", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.58", + "@rolldown/binding-darwin-x64": "1.0.0-beta.58", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.58", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.58", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.58", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.58", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.58", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.58", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.58", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.58", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.58", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.58" + } + }, "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", "dev": true, "license": "MIT", "dependencies": { @@ -8457,31 +8388,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" } }, @@ -8537,14 +8468,14 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", - "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", - "immutable": "^5.1.5", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -8588,9 +8519,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8766,35 +8697,35 @@ } }, "node_modules/sigstore": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.1.tgz", - "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.1", - "@sigstore/tuf": "^4.0.2", - "@sigstore/verify": "^3.1.1" + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" @@ -8831,20 +8762,20 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", - "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "~4.4.1", - "ws": "~8.20.1" + "ws": "~8.18.3" } }, "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8903,13 +8834,13 @@ } }, "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^10.1.1", + "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -8932,16 +8863,6 @@ "node": ">= 14" } }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -8983,6 +8904,17 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -8991,9 +8923,9 @@ "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9002,16 +8934,16 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "dev": true, "license": "ISC", "dependencies": { @@ -9032,9 +8964,9 @@ } }, "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "dev": true, "license": "MIT", "engines": { @@ -9060,14 +8992,14 @@ } }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=20" @@ -9104,10 +9036,23 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -9132,13 +9077,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -9148,9 +9093,9 @@ } }, "node_modules/tmp": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", - "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, "license": "MIT", "engines": { @@ -9202,42 +9147,24 @@ } }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^2.0.0", + "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -9276,13 +9203,48 @@ } }, "node_modules/undici": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", - "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/unique-filename": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/unique-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, "engines": { - "node": ">=18.17" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/universalify": { @@ -9345,6 +9307,17 @@ "node": ">= 0.4.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/validate-npm-package-name": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", @@ -9376,9 +9349,9 @@ } }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", "dev": true, "license": "MIT", "dependencies": { @@ -9461,9 +9434,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { @@ -9559,29 +9532,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz", - "integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "dev": true, "license": "ISC", "peerDependencies": { - "zod": "^3.25.28 || ^4" + "zod": "^3.25 || ^4" } }, "node_modules/zone.js": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.16.2.tgz", - "integrity": "sha512-Eky7p2Z1Ig3NnbfodSPoARCjKBSTFMnE/ACsP1L/XJEfY4SdOFce19BsUCWVwL6K5ABZFy5J3bjcMWffX+YM3Q==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", "license": "MIT" } } diff --git a/docs/src/assets/stackblitz/package.json b/docs/src/assets/stackblitz/package.json index e499b0b0f892..9b592c0a8fa5 100644 --- a/docs/src/assets/stackblitz/package.json +++ b/docs/src/assets/stackblitz/package.json @@ -10,25 +10,25 @@ }, "private": true, "dependencies": { - "@angular/cdk": "^22.0.0", - "@angular/common": "^22.0.0", - "@angular/compiler": "^22.0.0", - "@angular/core": "^22.0.0", - "@angular/forms": "^22.0.0", - "@angular/localize": "^22.0.0", - "@angular/material": "^22.0.0", - "@angular/material-luxon-adapter": "^22.0.0", - "@angular/platform-browser": "^22.0.0", - "@angular/router": "^22.0.0", + "@angular/cdk": "^21.1.0", + "@angular/common": "^21.1.0", + "@angular/compiler": "^21.1.0", + "@angular/core": "^21.1.0", + "@angular/forms": "^21.1.0", + "@angular/localize": "^21.1.0", + "@angular/material": "^21.1.0", + "@angular/material-luxon-adapter": "^21.1.0", + "@angular/platform-browser": "^21.1.0", + "@angular/router": "^21.1.0", "luxon": "^3.7.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", - "zone.js": "~0.16.2" + "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/cli": "^22.0.0", - "@angular/build": "^22.0.0", - "@angular/compiler-cli": "^22.0.0", + "@angular/cli": "^21.1.0", + "@angular/build": "^21.1.0", + "@angular/compiler-cli": "^21.1.0", "@types/jasmine": "~5.1.0", "@types/luxon": "^3.0.0", "@types/node": "^12.11.1", @@ -38,6 +38,6 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "~6.0.3" + "typescript": "~5.9.2" } } diff --git a/docs/src/assets/stackblitz/tsconfig.json b/docs/src/assets/stackblitz/tsconfig.json index 33830aaedb0b..61c430fae1b7 100644 --- a/docs/src/assets/stackblitz/tsconfig.json +++ b/docs/src/assets/stackblitz/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, + "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "bundler", "importHelpers": true, diff --git a/docs/src/assets/tokens/BUILD.bazel b/docs/src/assets/tokens/BUILD.bazel index fc5d09b3f87e..986fef96e3fb 100644 --- a/docs/src/assets/tokens/BUILD.bazel +++ b/docs/src/assets/tokens/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") copy_to_directory( name = "tokens", diff --git a/docs/src/index.html b/docs/src/index.html index b08d8a49f61c..853660012186 100644 --- a/docs/src/index.html +++ b/docs/src/index.html @@ -35,20 +35,9 @@ - - - - - - - - - - - - - - + + + diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 64a27b1a3b61..7fdc360d63b5 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -6,6 +6,7 @@ "allowSyntheticDefaultImports": true, "sourceMap": true, "declaration": false, + "downlevelIteration": true, "experimentalDecorators": true, "module": "es2022", "moduleResolution": "bundler", @@ -22,25 +23,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, - "useDefineForClassFields": false, - "paths": { - "@angular/aria": ["../src/aria"], - "@angular/aria/*": ["../src/aria/*"], - "@angular/cdk": ["../src/cdk"], - "@angular/cdk/*": ["../src/cdk/*"], - "@angular/material/*": ["../src/material/*"], - "@angular/material-experimental/*": ["../src/material-experimental/*"], - "@angular/material-experimental": ["../src/material-experimental"], - "@angular/cdk-experimental/*": ["../src/cdk-experimental/*"], - "@angular/cdk-experimental": ["../src/cdk-experimental"], - "@angular/material-moment-adapter": ["../src/material-moment-adapter"], - "@angular/material-luxon-adapter": ["../src/material-luxon-adapter"], - "@angular/material-date-fns-adapter": ["../src/material-date-fns-adapter"], - "@angular/components-examples": ["../src/components-examples"], - "@angular/components-examples/*": ["../src/components-examples/*"], - "@angular/google-maps": ["../src/google-maps"], - "@angular/youtube-player": ["../src/youtube-player"] - } + "useDefineForClassFields": false }, "exclude": ["src/assets", "dist", "bazel-*"], "angularCompilerOptions": { diff --git a/goldens/aria/accordion/index.api.md b/goldens/aria/accordion/index.api.md index 8fa6f80b9a57..2d5ea966bc61 100644 --- a/goldens/aria/accordion/index.api.md +++ b/goldens/aria/accordion/index.api.md @@ -7,8 +7,7 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; -import { Signal } from '@angular/core'; +import { WritableSignal } from '@angular/core'; // @public export class AccordionContent { @@ -19,22 +18,19 @@ export class AccordionContent { } // @public -export class AccordionGroup implements OnDestroy { +export class AccordionGroup { constructor(); collapseAll(): void; - readonly _collection: SortedCollection; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; expandAll(): void; readonly multiExpandable: _angular_core.InputSignalWithTransform; - // (undocumented) - ngOnDestroy(): void; readonly _pattern: AccordionGroupPattern; readonly softDisabled: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } @@ -42,22 +38,23 @@ export class AccordionGroup implements OnDestroy { // @public export class AccordionPanel { constructor(); + readonly _accordionTriggerPattern: WritableSignal; collapse(): void; - readonly element: HTMLElement; expand(): void; readonly id: _angular_core.InputSignal; - _pattern?: AccordionTriggerPattern; + readonly panelId: _angular_core.InputSignal; + readonly _pattern: AccordionPanelPattern; toggle(): void; readonly visible: _angular_core.Signal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // @public -export class AccordionTrigger implements OnInit, OnDestroy { - constructor(); +export class AccordionTrigger { + readonly _accordionPanelPattern: WritableSignal; readonly active: _angular_core.Signal; collapse(): void; readonly disabled: _angular_core.InputSignalWithTransform; @@ -65,16 +62,11 @@ export class AccordionTrigger implements OnInit, OnDestroy { expand(): void; readonly expanded: _angular_core.ModelSignal; readonly id: _angular_core.InputSignal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; - readonly panel: _angular_core.InputSignal; - readonly panelId: _angular_core.Signal; - _pattern: AccordionTriggerPattern; + readonly panelId: _angular_core.InputSignal; + readonly _pattern: AccordionTriggerPattern; toggle(): void; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } diff --git a/goldens/aria/accordion/testing/index.api.md b/goldens/aria/accordion/testing/index.api.md deleted file mode 100644 index ef4d0432fa9a..000000000000 --- a/goldens/aria/accordion/testing/index.api.md +++ /dev/null @@ -1,59 +0,0 @@ -## API Report File for "@angular/aria_accordion_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import * as _angular_cdk_testing from '@angular/cdk/testing'; -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class AccordionGroupHarness extends ComponentHarness { - getAccordions(filters?: AccordionHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - static with(options?: AccordionGroupHarnessFilters): HarnessPredicate; -} - -// @public -export interface AccordionGroupHarnessFilters extends BaseHarnessFilters { -} - -// @public -export class AccordionHarness extends ContentContainerComponentHarness { - blur(): Promise; - collapse(): Promise; - expand(): Promise; - focus(): Promise; - protected getRootHarnessLoader(): Promise<_angular_cdk_testing.HarnessLoader>; - getTitle(): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isExpanded(): Promise; - isFocused(): Promise; - toggle(): Promise; - static with(options?: AccordionHarnessFilters): HarnessPredicate; -} - -// @public -export interface AccordionHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - expanded?: boolean; - title?: string | RegExp; -} - -// @public -export enum AccordionSection { - // (undocumented) - PANEL = "[ngAccordionPanel]", - // (undocumented) - TRIGGER = "[ngAccordionTrigger]" -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/combobox/index.api.md b/goldens/aria/combobox/index.api.md index 9091f9477eed..57dbe14c8fe1 100644 --- a/goldens/aria/combobox/index.api.md +++ b/goldens/aria/combobox/index.api.md @@ -4,75 +4,75 @@ ```ts +import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; -import { ComboboxPattern } from '@angular/aria/private'; -import { ComboboxPopupPattern } from '@angular/aria/private'; -import { DeferredContentAware } from '@angular/aria/private'; -import * as i1 from '@angular/aria/private'; import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; // @public -export class Combobox extends DeferredContentAware implements OnInit { +export class Combobox { constructor(); readonly alwaysExpanded: _angular_core.InputSignalWithTransform; + close(): void; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; - readonly expanded: _angular_core.ModelSignal; - readonly inlineSuggestion: _angular_core.InputSignal; - // (undocumented) - ngOnInit(): void; - readonly _pattern: ComboboxPattern; - readonly _popup: _angular_core.WritableSignal; + readonly expanded: _angular_core.Signal; + filterMode: _angular_core.InputSignal<"manual" | "auto-select" | "highlight">; + readonly firstMatch: _angular_core.InputSignal; + readonly inputElement: _angular_core.Signal; + open(): void; + readonly _pattern: ComboboxPattern; + readonly popup: _angular_core.Signal | undefined>; readonly readonly: _angular_core.InputSignalWithTransform; - _registerPopup(popup: ComboboxPopup): void; - readonly softDisabled: _angular_core.InputSignalWithTransform; - readonly tabIndex: _angular_core.InputSignalWithTransform; - _unregisterPopup(): void; - readonly value: _angular_core.ModelSignal; + protected textDirection: _angular_core.Signal<_angular_cdk_bidi.Direction>; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngCombobox]", ["ngCombobox"], { "filterMode": { "alias": "filterMode"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "firstMatch": { "alias": "firstMatch"; "required": false; "isSignal": true; }; "alwaysExpanded": { "alias": "alwaysExpanded"; "required": false; "isSignal": true; }; }, {}, ["popup"], never, true, [{ directive: typeof DeferredContentAware; inputs: { "preserveContent": "preserveContent"; }; outputs: {}; }]>; // (undocumented) - static ɵfac: _angular_core.ɵɵFactoryDeclaration; + static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } // @public -export class ComboboxPopup implements OnInit, OnDestroy { - readonly activeDescendant: _angular_core.Signal; - readonly combobox: _angular_core.InputSignal; - readonly controlTarget: _angular_core.Signal; +export class ComboboxDialog { + constructor(); // (undocumented) - ngOnDestroy(): void; + close(): void; + readonly combobox: Combobox; + readonly element: HTMLElement; // (undocumented) - ngOnInit(): void; - readonly _pattern: ComboboxPopupPattern; - readonly popupId: _angular_core.Signal; - readonly popupType: _angular_core.InputSignal<"listbox" | "tree" | "grid" | "dialog">; - _registerWidget(widget: ComboboxWidget): void; - _unregisterWidget(): void; - readonly _widget: _angular_core.WritableSignal; + _pattern: ComboboxDialogPattern; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) - static ɵfac: _angular_core.ɵɵFactoryDeclaration; + static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // @public -export class ComboboxWidget implements OnInit, OnDestroy { +export class ComboboxInput { constructor(); - readonly activeDescendant: _angular_core.InputSignal; + readonly combobox: Combobox; readonly element: HTMLElement; + value: _angular_core.ModelSignal; // (undocumented) - ngOnDestroy(): void; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) - ngOnInit(): void; - onFocusin(): void; - onFocusout(event: FocusEvent): void; - readonly popupId: _angular_core.WritableSignal; + static ɵfac: _angular_core.ɵɵFactoryDeclaration; +} + +// @public +export class ComboboxPopup { + readonly combobox: Combobox | null; + readonly _controls: _angular_core.WritableSignal | ComboboxTreeControls | ComboboxDialogPattern | undefined>; + // (undocumented) + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngComboboxPopup]", ["ngComboboxPopup"], {}, {}, never, never, true, never>; + // (undocumented) + static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; +} + +// @public +export class ComboboxPopupContainer { // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) - static ɵfac: _angular_core.ɵɵFactoryDeclaration; + static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // (No @packageDocumentation comment for this package) diff --git a/goldens/aria/combobox/testing/index.api.md b/goldens/aria/combobox/testing/index.api.md deleted file mode 100644 index b732c006dc99..000000000000 --- a/goldens/aria/combobox/testing/index.api.md +++ /dev/null @@ -1,47 +0,0 @@ -## API Report File for "@angular/aria_combobox_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ComponentHarnessConstructor } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class ComboboxHarness extends ContentContainerComponentHarness { - blur(): Promise; - close(): Promise; - focus(): Promise; - getPlaceholder(): Promise; - getPopupLoader(): Promise; - getPopupWidget(type: ComponentHarnessConstructor & { - with: (options?: { - selector?: string; - }) => HarnessPredicate; - }): Promise; - protected getRootHarnessLoader(): Promise; - getValue(): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isFocused(): Promise; - isOpen(): Promise; - open(): Promise; - setValue(value: string): Promise; - static with(options?: ComboboxHarnessFilters): HarnessPredicate; -} - -// @public -export interface ComboboxHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - placeholder?: string | RegExp; - value?: string | RegExp; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/grid/index.api.md b/goldens/aria/grid/index.api.md index 3d7839b9a1de..4eb62d5cb7d4 100644 --- a/goldens/aria/grid/index.api.md +++ b/goldens/aria/grid/index.api.md @@ -7,38 +7,31 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { ElementRef } from '@angular/core'; -import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; import { Signal } from '@angular/core'; // @public -export class Grid implements OnDestroy { +export class Grid { constructor(); - readonly activeDescendant: Signal; - readonly _collection: SortedCollection; readonly colWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; + readonly enableRangeSelection: _angular_core.InputSignalWithTransform; readonly enableSelection: _angular_core.InputSignalWithTransform; readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; readonly multi: _angular_core.InputSignalWithTransform; - // (undocumented) - ngOnDestroy(): void; readonly _pattern: GridPattern; readonly rowWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">; - scrollActiveCellIntoView(options?: ScrollIntoViewOptions): void; readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">; readonly softDisabled: _angular_core.InputSignalWithTransform; - readonly tabIndex: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // @public -export class GridCell implements OnInit, OnDestroy { +export class GridCell { constructor(); readonly active: Signal; readonly colIndex: _angular_core.InputSignal; @@ -46,10 +39,7 @@ export class GridCell implements OnInit, OnDestroy { readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly id: _angular_core.InputSignal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; + readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: GridCellPattern; readonly role: _angular_core.InputSignal<"gridcell" | "columnheader" | "rowheader">; readonly rowIndex: _angular_core.InputSignal; @@ -59,8 +49,9 @@ export class GridCell implements OnInit, OnDestroy { protected readonly _tabIndex: Signal; readonly tabindex: _angular_core.InputSignal; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; + readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } @@ -69,13 +60,13 @@ export class GridCell implements OnInit, OnDestroy { export class GridCellWidget { constructor(); activate(): void; - readonly activated: _angular_core.OutputEmitterRef; + readonly activated: _angular_core.OutputEmitterRef; readonly active: Signal; deactivate(): void; - readonly deactivated: _angular_core.OutputEmitterRef; + readonly deactivated: _angular_core.OutputEmitterRef; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; - readonly focusTarget: _angular_core.InputSignal>; + readonly focusTarget: _angular_core.InputSignal | HTMLElement | undefined>; readonly id: _angular_core.InputSignal; get isActivated(): Signal; readonly _pattern: GridCellWidgetPattern; @@ -89,19 +80,13 @@ export class GridCellWidget { } // @public -export class GridRow implements OnInit, OnDestroy { - constructor(); - readonly _collection: SortedCollection; +export class GridRow { readonly element: HTMLElement; readonly _gridPattern: Signal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; readonly _pattern: GridRowPattern; readonly rowIndex: _angular_core.InputSignal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } diff --git a/goldens/aria/grid/testing/index.api.md b/goldens/aria/grid/testing/index.api.md deleted file mode 100644 index 33ea212086a3..000000000000 --- a/goldens/aria/grid/testing/index.api.md +++ /dev/null @@ -1,66 +0,0 @@ -## API Report File for "@angular/aria_grid_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class GridCellHarness extends ContentContainerComponentHarness { - blur(): Promise; - click(): Promise; - focus(): Promise; - getText(): Promise; - // (undocumented) - static hostSelector: string; - isActive(): Promise; - isDisabled(): Promise; - isFocused(): Promise; - isSelected(): Promise; - static with(options?: GridCellHarnessFilters): HarnessPredicate; -} - -// @public -export interface GridCellHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - selected?: boolean; - text?: string | RegExp; -} - -// @public -export class GridHarness extends ComponentHarness { - getCells(filters?: GridCellHarnessFilters): Promise; - getCellTextByIndex(): Promise; - getRows(filters?: GridRowHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isMultiSelectable(): Promise; - static with(options?: GridHarnessFilters): HarnessPredicate; -} - -// @public -export interface GridHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; -} - -// @public -export class GridRowHarness extends ComponentHarness { - getCells(filters?: GridCellHarnessFilters): Promise; - getCellTextByIndex(filters?: GridCellHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - static with(options?: GridRowHarnessFilters): HarnessPredicate; -} - -// @public -export interface GridRowHarnessFilters extends BaseHarnessFilters { -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/listbox/index.api.md b/goldens/aria/listbox/index.api.md index 54232cffaefe..e81629c4008c 100644 --- a/goldens/aria/listbox/index.api.md +++ b/goldens/aria/listbox/index.api.md @@ -6,56 +6,47 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; -import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; -import { Signal } from '@angular/core'; // @public -export class Listbox implements OnDestroy { +export class Listbox { constructor(); - readonly activeDescendant: Signal; - readonly _collection: SortedCollection>; - readonly disabled: _angular_core.InputSignalWithTransform; + disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; - readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; + focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; gotoFirst(): void; - gotoIndex(index: number): void; readonly id: _angular_core.InputSignal; - readonly multi: _angular_core.InputSignalWithTransform; + protected items: _angular_core.Signal[]>; + multi: _angular_core.InputSignalWithTransform; // (undocumented) - ngOnDestroy(): void; - readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; + _onFocus(): void; + orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: ListboxPattern; - readonly readonly: _angular_core.InputSignalWithTransform; + readonly: _angular_core.InputSignalWithTransform; // (undocumented) scrollActiveItemIntoView(options?: ScrollIntoViewOptions): void; - readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">; - readonly softDisabled: _angular_core.InputSignalWithTransform; - readonly tabIndex: _angular_core.InputSignalWithTransform; - protected readonly textDirection: Signal<_angular_cdk_bidi.Direction>; - readonly typeaheadDelay: _angular_core.InputSignal; - readonly value: _angular_core.ModelSignal; - readonly wrap: _angular_core.InputSignalWithTransform; + selectionMode: _angular_core.InputSignal<"follow" | "explicit">; + softDisabled: _angular_core.InputSignalWithTransform; + protected textDirection: _angular_core.Signal<_angular_cdk_bidi.Direction>; + typeaheadDelay: _angular_core.InputSignal; + values: _angular_core.ModelSignal; + wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; }, ["_options"], never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } // @public -class Option_2 implements OnInit, OnDestroy { - readonly active: _angular_core.Signal; - readonly disabled: _angular_core.InputSignalWithTransform; +class Option_2 { + active: _angular_core.Signal; + disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly id: _angular_core.InputSignal; - readonly label: _angular_core.InputSignal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; + label: _angular_core.InputSignal; readonly _pattern: OptionPattern; + protected searchTerm: _angular_core.Signal; readonly selected: _angular_core.Signal; - readonly value: _angular_core.InputSignal; + value: _angular_core.InputSignal; // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngOption]", ["ngOption"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>; // (undocumented) diff --git a/goldens/aria/listbox/testing/index.api.md b/goldens/aria/listbox/testing/index.api.md deleted file mode 100644 index e9b69ef2b102..000000000000 --- a/goldens/aria/listbox/testing/index.api.md +++ /dev/null @@ -1,50 +0,0 @@ -## API Report File for "@angular/aria_listbox_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class ListboxHarness extends ComponentHarness { - blur(): Promise; - focus(): Promise; - getActiveDescendantId(): Promise; - getOptions(filters?: ListboxOptionHarnessFilters): Promise; - getOrientation(): Promise<'vertical' | 'horizontal'>; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isMulti(): Promise; - static with(options?: ListboxHarnessFilters): HarnessPredicate; -} - -// @public -export interface ListboxHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; -} - -// @public -export class ListboxOptionHarness extends ComponentHarness { - click(): Promise; - getText(): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isSelected(): Promise; - static with(options?: ListboxOptionHarnessFilters): HarnessPredicate; -} - -// @public -export interface ListboxOptionHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - selected?: boolean; - text?: string | RegExp; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/menu/index.api.md b/goldens/aria/menu/index.api.md index 79630b052dba..8c3ca02bb41f 100644 --- a/goldens/aria/menu/index.api.md +++ b/goldens/aria/menu/index.api.md @@ -7,56 +7,50 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; import { Signal } from '@angular/core'; // @public -export class Menu implements OnDestroy { +export class Menu { constructor(); + readonly _allItems: Signal[]>; close(): void; - readonly _collection: SortedCollection>; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly expansionDelay: _angular_core.InputSignal; readonly id: _angular_core.InputSignal; readonly _items: Signal[]>; readonly itemSelected: _angular_core.OutputEmitterRef; - // (undocumented) - ngOnDestroy(): void; readonly parent: _angular_core.WritableSignal | MenuItem | undefined>; readonly _pattern: MenuPattern; - readonly softDisabled: _angular_core.InputSignalWithTransform; readonly tabIndex: Signal<0 | -1>; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; readonly typeaheadDelay: _angular_core.InputSignal; readonly visible: Signal; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenu]", ["ngMenu"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "expansionDelay": { "alias": "expansionDelay"; "required": false; "isSignal": true; }; }, { "itemSelected": "itemSelected"; }, never, never, true, [{ directive: typeof DeferredContentAware; inputs: { "preserveContent": "preserveContent"; }; outputs: {}; }]>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenu]", ["ngMenu"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "expansionDelay": { "alias": "expansionDelay"; "required": false; "isSignal": true; }; }, { "itemSelected": "itemSelected"; }, ["_allItems"], never, true, [{ directive: typeof DeferredContentAware; inputs: { "preserveContent": "preserveContent"; }; outputs: {}; }]>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } // @public -export class MenuBar implements OnDestroy { +export class MenuBar { constructor(); + readonly _allItems: _angular_core.Signal[]>; close(): void; - readonly _collection: SortedCollection>; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; // (undocumented) readonly _items: SignalLike[]>; readonly itemSelected: _angular_core.OutputEmitterRef; - // (undocumented) - ngOnDestroy(): void; readonly _pattern: MenuBarPattern; readonly softDisabled: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; readonly typeaheadDelay: _angular_core.InputSignal; - readonly value: _angular_core.ModelSignal; + readonly values: _angular_core.ModelSignal; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenuBar]", ["ngMenuBar"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "itemSelected": "itemSelected"; }, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenuBar]", ["ngMenuBar"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; "itemSelected": "itemSelected"; }, ["_allItems"], never, true, never>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } @@ -70,7 +64,7 @@ export class MenuContent { } // @public -export class MenuItem implements OnInit, OnDestroy { +export class MenuItem { constructor(); readonly active: _angular_core.Signal; close(): void; @@ -79,19 +73,14 @@ export class MenuItem implements OnInit, OnDestroy { readonly expanded: _angular_core.Signal; readonly hasPopup: _angular_core.Signal; readonly id: _angular_core.InputSignal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; open(): void; readonly parent: Menu | MenuBar | null; readonly _pattern: MenuItemPattern; - readonly role: _angular_core.InputSignal<"menuitem" | "menuitemradio" | "menuitemcheckbox">; readonly searchTerm: _angular_core.ModelSignal; readonly submenu: _angular_core.InputSignal | undefined>; readonly value: _angular_core.InputSignal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenuItem]", ["ngMenuItem"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchTerm": { "alias": "searchTerm"; "required": false; "isSignal": true; }; "role": { "alias": "role"; "required": false; "isSignal": true; }; "submenu": { "alias": "submenu"; "required": false; "isSignal": true; }; }, { "searchTerm": "searchTermChange"; }, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngMenuItem]", ["ngMenuItem"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchTerm": { "alias": "searchTerm"; "required": false; "isSignal": true; }; "submenu": { "alias": "submenu"; "required": false; "isSignal": true; }; }, { "searchTerm": "searchTermChange"; }, never, never, true, never>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } @@ -104,9 +93,9 @@ export class MenuTrigger { readonly element: HTMLElement; readonly expanded: _angular_core.Signal; readonly hasPopup: _angular_core.Signal; - readonly menu: _angular_core.InputSignal | undefined>; + menu: _angular_core.InputSignal | undefined>; open(): void; - readonly _pattern: MenuTriggerPattern; + _pattern: MenuTriggerPattern; readonly softDisabled: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; // (undocumented) diff --git a/goldens/aria/menu/testing/index.api.md b/goldens/aria/menu/testing/index.api.md deleted file mode 100644 index 3a687bdcf2b2..000000000000 --- a/goldens/aria/menu/testing/index.api.md +++ /dev/null @@ -1,55 +0,0 @@ -## API Report File for "@angular/aria_menu_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; -import { TestElement } from '@angular/cdk/testing'; - -// @public -export class MenuHarness extends ComponentHarness { - close(): Promise; - getItems(filters?: MenuItemHarnessFilters): Promise; - _getTrigger(): Promise; - // (undocumented) - static hostSelector: string; - isMenuBar(): Promise; - isOpen(): Promise; - open(): Promise; - // (undocumented) - static with(options?: MenuHarnessFilters): HarnessPredicate; -} - -// @public -export interface MenuHarnessFilters extends BaseHarnessFilters { - triggerText?: string | RegExp; -} - -// @public -export class MenuItemHarness extends ComponentHarness { - click(): Promise; - getSubmenu(): Promise; - getText(): Promise; - hasSubmenu(): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - isExpanded(): Promise; - isFocused(): Promise; - // (undocumented) - static with(options?: MenuItemHarnessFilters): HarnessPredicate; -} - -// @public -export interface MenuItemHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - expanded?: boolean; - text?: string | RegExp; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/private/index.api.md b/goldens/aria/private/index.api.md index 9fb23800cbd8..3635c307d839 100644 --- a/goldens/aria/private/index.api.md +++ b/goldens/aria/private/index.api.md @@ -5,40 +5,54 @@ ```ts import * as _angular_core from '@angular/core'; -import { ElementRef } from '@angular/core'; import { OnDestroy } from '@angular/core'; -import { Signal } from '@angular/core'; import { untracked } from '@angular/core/primitives/signals'; // @public export interface AccordionGroupInputs extends Omit & ListFocusInputs & Omit, 'focusMode'> { + getItem: (e: Element | null | undefined) => AccordionTriggerPattern | undefined; } // @public export class AccordionGroupPattern { constructor(inputs: AccordionGroupInputs); - readonly click: SignalLike>; - collapseAll(): void; - expandAll(): void; readonly expansionBehavior: ListExpansion; readonly focusBehavior: ListFocus; // (undocumented) readonly inputs: AccordionGroupInputs; - readonly keydown: SignalLike>; + keydown: SignalLike>; readonly navigationBehavior: ListNavigation; - readonly nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; - onClick(event: PointerEvent): void; + nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; onFocus(event: FocusEvent): void; onKeydown(event: KeyboardEvent): void; - readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; + onPointerdown(event: PointerEvent): void; + pointerdown: SignalLike>; + prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; toggle(): void; - validate(): string[]; +} + +// @public +export interface AccordionPanelInputs { + accordionTrigger: SignalLike; + id: SignalLike; + panelId: SignalLike; +} + +// @public +export class AccordionPanelPattern { + constructor(inputs: AccordionPanelInputs); + accordionTrigger: SignalLike; + hidden: SignalLike; + id: SignalLike; + // (undocumented) + readonly inputs: AccordionPanelInputs; } // @public export interface AccordionTriggerInputs extends Omit, Omit { accordionGroup: SignalLike; - accordionPanelId: SignalLike; + accordionPanel: SignalLike; + panelId: SignalLike; } // @public @@ -46,13 +60,14 @@ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusIte constructor(inputs: AccordionTriggerInputs); readonly active: SignalLike; close(): void; - readonly controls: SignalLike; + readonly controls: SignalLike; readonly disabled: SignalLike; readonly element: SignalLike; readonly expandable: SignalLike; readonly expanded: WritableSignalLike; readonly hardDisabled: SignalLike; readonly id: SignalLike; + readonly index: SignalLike; // (undocumented) readonly inputs: AccordionTriggerInputs; open(): void; @@ -60,72 +75,208 @@ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusIte toggle(): void; } +// @public (undocumented) +export class ComboboxDialogPattern { + constructor(inputs: { + combobox: ComboboxPattern; + element: SignalLike; + id: SignalLike; + }); + // (undocumented) + id: () => string; + // (undocumented) + readonly inputs: { + combobox: ComboboxPattern; + element: SignalLike; + id: SignalLike; + }; + // (undocumented) + keydown: SignalLike>; + // (undocumented) + onClick(event: MouseEvent): void; + // (undocumented) + onKeydown(event: KeyboardEvent): void; + // (undocumented) + role: () => "dialog"; +} + // @public -export interface ComboboxInputs extends ExpansionItem { +export interface ComboboxInputs, V> { alwaysExpanded: SignalLike; + containerEl: SignalLike; disabled: SignalLike; - element: SignalLike; - inlineSuggestion: SignalLike; - popup: SignalLike; + filterMode: SignalLike<'manual' | 'auto-select' | 'highlight'>; + firstMatch: SignalLike; + inputEl: SignalLike; + inputValue?: WritableSignalLike; + popupControls: SignalLike | ComboboxTreeControls | ComboboxDialogPattern | undefined>; readonly: SignalLike; - softDisabled?: SignalLike; - value: WritableSignalLike; + textDirection: SignalLike<'rtl' | 'ltr'>; } // @public -export class ComboboxPattern { - constructor(inputs: ComboboxInputs); - readonly activeDescendant: _angular_core.Signal; - readonly ariaReadonly: _angular_core.Signal<"true" | null>; - readonly autocomplete: _angular_core.Signal<"none" | "inline" | "list" | "both">; - click: _angular_core.Signal>; - readonly disabled: () => boolean; - readonly element: () => HTMLElement; - highlightEffect(): void; - readonly inlineSuggestion: () => string | undefined; - // (undocumented) - readonly inputs: ComboboxInputs; - readonly isDeleting: _angular_core.WritableSignal; - readonly isEditable: _angular_core.Signal; - readonly isExpanded: _angular_core.Signal; - readonly isFocused: _angular_core.WritableSignal; - readonly keyboardEventRelay: _angular_core.WritableSignal; - keyboardEventRelayEffect(): void; - keydown: _angular_core.Signal>; - readonly nativeDisabled: _angular_core.Signal<"" | null>; - readonly nativeReadonly: _angular_core.Signal<"" | null>; - onClick(event: PointerEvent): void; - onFocusin(): void; - onFocusout(): void; +export interface ComboboxListboxControls, V> { + activeId: SignalLike; + clearSelection: () => void; + first: () => void; + focus: (item: T, opts?: { + focusElement?: boolean; + }) => void; + getActiveItem: () => T | undefined; + getItem: (e: PointerEvent) => T | undefined; + getSelectedItems: () => T[]; + id: () => string; + items: SignalLike; + last: () => void; + multi: SignalLike; + next: () => void; + prev: () => void; + role: SignalLike<'listbox' | 'tree' | 'grid'>; + select: (item?: T) => void; + setValue: (value: V | undefined) => void; + toggle: (item?: T) => void; + unfocus: () => void; +} + +// @public (undocumented) +export type ComboboxListboxInputs = ListboxInputs & { + combobox: SignalLike, V> | undefined>; +}; + +// @public (undocumented) +export class ComboboxListboxPattern extends ListboxPattern implements ComboboxListboxControls, V> { + constructor(inputs: ComboboxListboxInputs); + activeId: SignalLike; + clearSelection: () => void; + first: () => void; + focus: (item: OptionPattern, opts?: { + focusElement?: boolean; + }) => void; + getActiveItem: () => OptionPattern | undefined; + getItem: (e: PointerEvent) => OptionPattern | undefined; + getSelectedItems: () => OptionPattern[]; + id: SignalLike; + // (undocumented) + readonly inputs: ComboboxListboxInputs; + items: SignalLike[]>; + last: () => void; + multi: SignalLike; + next: () => void; + onKeydown(_: KeyboardEvent): void; + onPointerdown(_: PointerEvent): void; + prev: () => void; + role: SignalLike<"listbox">; + select: (item?: OptionPattern) => void; + setDefaultState(): void; + setValue: (value: V | undefined) => void; + tabIndex: SignalLike<-1 | 0>; + toggle: (item?: OptionPattern) => void; + unfocus: () => void; +} + +// @public +export class ComboboxPattern, V> { + constructor(inputs: ComboboxInputs); + activeDescendant: SignalLike; + autocomplete: SignalLike<"both" | "list">; + click: SignalLike>; + close(opts?: { + reset: boolean; + }): void; + collapseItem(): void; + collapseKey: SignalLike<"ArrowLeft" | "ArrowRight">; + commit(): void; + disabled: () => boolean; + expanded: WritableSignalLike; + expandItem(): void; + expandKey: SignalLike<"ArrowLeft" | "ArrowRight">; + first(): void; + firstMatch: SignalLike; + hasBeenFocused: WritableSignalLike; + hasPopup: SignalLike<"listbox" | "tree" | "grid" | "dialog" | null>; + highlight(): void; + highlightedItem: WritableSignalLike; + // (undocumented) + readonly inputs: ComboboxInputs; + isDeleting: boolean; + isFocused: WritableSignalLike; + keydown: SignalLike>; + last(): void; + listControls: () => ComboboxListboxControls | null | undefined; + next(): void; + onClick(event: MouseEvent): void; + onFilter(): void; + onFocusIn(): void; + onFocusOut(event: FocusEvent): void; onInput(event: Event): void; onKeydown(event: KeyboardEvent): void; - readonly popupId: _angular_core.Signal; - readonly popupType: _angular_core.Signal<"listbox" | "tree" | "grid" | "dialog" | undefined>; - readonly readonly: () => boolean; - readonly softDisabled: () => boolean; - readonly value: WritableSignalLike; + open(nav?: { + first?: boolean; + last?: boolean; + selected?: boolean; + }): void; + popupId: SignalLike; + prev(): void; + readonly: SignalLike; + select(opts?: { + item?: T; + commit?: boolean; + close?: boolean; + }): void; + treeControls: () => ComboboxTreeControls | null; } -// @public -export interface ComboboxPopupInputs { - activeDescendant: SignalLike; - controlTarget: SignalLike; - popupId: SignalLike; - popupType: SignalLike<'listbox' | 'tree' | 'grid' | 'dialog'>; +// @public (undocumented) +export interface ComboboxTreeControls, V> extends ComboboxListboxControls { + collapseAll: () => void; + collapseItem: () => void; + expandAll: () => void; + expandItem: () => void; + isItemCollapsible: () => boolean; + isItemExpandable: (item?: T) => boolean; + isItemSelectable: (item?: T) => boolean; + toggleExpansion: (item?: T) => void; } -// @public -export class ComboboxPopupPattern { - constructor(inputs: ComboboxPopupInputs); - readonly activeDescendant: () => string | undefined; - readonly controlTarget: () => HTMLElement | undefined; - // (undocumented) - readonly inputs: ComboboxPopupInputs; - readonly isFocused: _angular_core.WritableSignal; - onFocusin(): void; - onFocusout(event: FocusEvent): void; - readonly popupId: () => string | undefined; - readonly popupType: () => "listbox" | "tree" | "grid" | "dialog"; +// @public (undocumented) +export type ComboboxTreeInputs = TreeInputs & { + combobox: SignalLike, V> | undefined>; +}; + +// @public (undocumented) +export class ComboboxTreePattern extends TreePattern implements ComboboxTreeControls, V> { + constructor(inputs: ComboboxTreeInputs); + // (undocumented) + activeId: SignalLike; + clearSelection: () => void; + collapseAll: () => void; + collapseItem: () => void; + expandAll: () => void; + expandItem: () => void; + first: () => void; + focus: (item: TreeItemPattern) => void; + getActiveItem: () => TreeItemPattern | undefined; + getItem: (e: PointerEvent) => TreeItemPattern | undefined; + getSelectedItems: () => TreeItemPattern[]; + // (undocumented) + readonly inputs: ComboboxTreeInputs; + isItemCollapsible: () => boolean; + isItemExpandable(item?: TreeItemPattern | undefined): boolean; + isItemSelectable: (item?: TreeItemPattern | undefined) => boolean; + items: SignalLike[]>; + last: () => void; + next: () => void; + onKeydown(_: KeyboardEvent): void; + onPointerdown(_: PointerEvent): void; + prev: () => void; + role: () => "tree"; + select: (item?: TreeItemPattern) => void; + setDefaultState(): void; + setValue: (value: V | undefined) => void; + tabIndex: SignalLike<-1 | 0>; + toggle: (item?: TreeItemPattern) => void; + toggleExpansion: (item?: TreeItemPattern) => void; + unfocus: () => void; } // @public (undocumented) @@ -160,22 +311,20 @@ export class DeferredContentAware { } // @public -export type ElementResolver = ElementRef | T | undefined | null | ((context: HTMLElement) => T | null | undefined); - -// @public -export interface GridCellInputs extends GridCell { +export interface GridCellInputs extends GridCell, Omit, 'focusMode' | 'items' | 'activeItem' | 'softDisabled' | 'element'> { colIndex: SignalLike; getWidget: (e: Element | null) => GridCellWidgetPattern | undefined; grid: SignalLike; row: SignalLike; rowIndex: SignalLike; - widget: SignalLike; + widgets: SignalLike; } // @public export class GridCellPattern implements GridCell { constructor(inputs: GridCellInputs); readonly active: SignalLike; + readonly activeWidget: WritableSignalLike; readonly anchor: SignalLike; readonly ariaColIndex: SignalLike; readonly ariaRowIndex: SignalLike; @@ -184,44 +333,52 @@ export class GridCellPattern implements GridCell { readonly disabled: SignalLike; readonly element: SignalLike; focus(): void; + readonly focusBehavior: ListFocus; readonly id: SignalLike; // (undocumented) readonly inputs: GridCellInputs; readonly isActivated: SignalLike; readonly isFocused: WritableSignalLike; + readonly keydown: SignalLike>; + readonly multiWidgetMode: SignalLike; + readonly navigationActivated: WritableSignalLike; + readonly navigationBehavior: ListNavigation; + readonly navigationDisabled: SignalLike; + readonly nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; onFocusIn(event: FocusEvent): void; onFocusOut(event: FocusEvent): void; onKeydown(event: KeyboardEvent): void; + readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; readonly rowSpan: SignalLike; readonly selectable: SignalLike; readonly selected: WritableSignalLike; + readonly singleWidgetMode: SignalLike; + startNavigation(): void; + stopNavigation(): void; readonly tabIndex: SignalLike<-1 | 0>; - readonly widget: SignalLike; + readonly widgetActivated: SignalLike; widgetTabIndex(): -1 | 0; } // @public -export interface GridCellWidgetInputs { +export interface GridCellWidgetInputs extends Omit { cell: SignalLike; - disabled: SignalLike; element: SignalLike; - focusTarget: SignalLike>; - onActivate?: (event: KeyboardEvent | FocusEvent | undefined) => void; - onDeactivate?: (event: KeyboardEvent | FocusEvent | undefined) => void; + focusTarget: SignalLike; widgetType: SignalLike<'simple' | 'complex' | 'editable'>; } // @public -export class GridCellWidgetPattern { +export class GridCellWidgetPattern implements ListNavigationItem { constructor(inputs: GridCellWidgetInputs); activate(event?: KeyboardEvent | FocusEvent): void; - activationEffect(): void; readonly active: SignalLike; deactivate(event?: KeyboardEvent | FocusEvent): void; - deactivationEffect(): void; readonly disabled: SignalLike; readonly element: SignalLike; focus(): void; + readonly id: SignalLike; + readonly index: SignalLike; // (undocumented) readonly inputs: GridCellWidgetInputs; readonly isActivated: WritableSignalLike; @@ -238,6 +395,7 @@ export class GridCellWidgetPattern { // @public export interface GridInputs extends Omit, 'cells'> { element: SignalLike; + enableRangeSelection: SignalLike; enableSelection: SignalLike; getCell: (e: Element | null) => GridCellPattern | undefined; multi: SignalLike; @@ -249,34 +407,36 @@ export interface GridInputs extends Omit, 'cells'> // @public export class GridPattern { constructor(inputs: GridInputs); + readonly acceptsPointerMove: SignalLike; readonly activeCell: SignalLike; readonly activeDescendant: SignalLike; readonly anchorCell: SignalLike; readonly cells: SignalLike; - readonly clickManager: SignalLike>; readonly disabled: SignalLike; readonly dragging: WritableSignalLike; focusEffect(): void; readonly gridBehavior: Grid; - readonly hasBeenInteracted: WritableSignalLike; + readonly hasBeenFocused: WritableSignalLike; // (undocumented) readonly inputs: GridInputs; readonly isFocused: WritableSignalLike; readonly keydown: SignalLike>; - readonly multiSelectable: SignalLike; readonly nextColKey: SignalLike<"ArrowRight" | "ArrowLeft">; - onClick(event: PointerEvent): void; onFocusIn(event: FocusEvent): void; onFocusOut(event: FocusEvent): void; onKeydown(event: KeyboardEvent): void; + onPointerdown(event: PointerEvent): void; + onPointermove(event: PointerEvent): void; + onPointerup(event: PointerEvent): void; readonly pauseNavigation: SignalLike; + readonly pointerdown: SignalLike>; + readonly pointerup: SignalLike>; readonly prevColKey: SignalLike<"ArrowRight" | "ArrowLeft">; resetFocusEffect(): void; resetStateEffect(): void; restoreFocusEffect(): void; setDefaultStateEffect(): void; readonly tabIndex: SignalLike<0 | -1>; - validate(): string[]; } // @public @@ -294,12 +454,6 @@ export class GridRowPattern { rowIndex: SignalLike; } -// @public (undocumented) -export interface HasElement { - // (undocumented) - element: HTMLElement; -} - // @public (undocumented) export function linkedSignal(sourceFn: () => T): WritableSignalLike; @@ -312,36 +466,32 @@ export type ListboxInputs = ListInputs, V> & { // @public export class ListboxPattern { constructor(inputs: ListboxInputs); - readonly activeDescendant: SignalLike; - readonly clickManager: SignalLike>; - readonly disabled: SignalLike; - readonly dynamicSpaceKey: SignalLike<"" | " ">; - readonly followFocus: SignalLike; + activeDescendant: SignalLike; + disabled: SignalLike; + dynamicSpaceKey: SignalLike<"" | " ">; + followFocus: SignalLike; // (undocumented) protected _getItem(e: PointerEvent): OptionPattern | undefined; - readonly hasBeenInteracted: WritableSignalLike; // (undocumented) readonly inputs: ListboxInputs; - readonly keydown: SignalLike>; + keydown: SignalLike>; // (undocumented) - readonly listBehavior: List, V>; + listBehavior: List, V>; multi: SignalLike; - readonly nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; - // (undocumented) - onClick(event: PointerEvent): void; - // (undocumented) - onFocusIn(): void; + nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; onKeydown(event: KeyboardEvent): void; - readonly orientation: SignalLike<'vertical' | 'horizontal'>; - readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; - readonly readonly: SignalLike; + // (undocumented) + onPointerdown(event: PointerEvent): void; + orientation: SignalLike<'vertical' | 'horizontal'>; + pointerdown: SignalLike>; + prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; + readonly: SignalLike; setDefaultState(): void; - setDefaultStateEffect(): void; - readonly setsize: SignalLike; - readonly tabIndex: SignalLike<-1 | 0>; - readonly typeaheadRegexp: RegExp; + setsize: SignalLike; + tabIndex: SignalLike<-1 | 0>; + typeaheadRegexp: RegExp; validate(): string[]; - readonly wrap: WritableSignalLike; + wrap: WritableSignalLike; } // @public @@ -355,17 +505,17 @@ export interface MenuBarInputs extends ListInputs, V> { export class MenuBarPattern { constructor(inputs: MenuBarInputs); close(): void; - readonly disabled: () => boolean; - readonly dynamicSpaceKey: SignalLike<"" | " ">; + disabled: () => boolean; + dynamicSpaceKey: SignalLike<"" | " ">; goto(item: MenuItemPattern, opts?: { focusElement?: boolean; }): void; - readonly hasBeenInteracted: WritableSignalLike; + hasBeenFocused: WritableSignalLike; // (undocumented) readonly inputs: MenuBarInputs; - readonly isFocused: WritableSignalLike; - readonly keydownManager: SignalLike>; - readonly listBehavior: List, V>; + isFocused: WritableSignalLike; + keydownManager: SignalLike>; + listBehavior: List, V>; next(): void; onClick(event: MouseEvent): void; onFocusIn(): void; @@ -374,13 +524,12 @@ export class MenuBarPattern { onMouseOver(event: MouseEvent): void; prev(): void; setDefaultState(): void; - setDefaultStateEffect(): void; - readonly tabIndex: () => 0 | -1; - readonly typeaheadRegexp: RegExp; + tabIndex: () => 0 | -1; + typeaheadRegexp: RegExp; } // @public -export interface MenuInputs extends Omit, V>, 'value'> { +export interface MenuInputs extends Omit, V>, 'values'> { expansionDelay: SignalLike; id: SignalLike; items: SignalLike[]>; @@ -392,26 +541,25 @@ export interface MenuInputs extends Omit, V>, ' // @public export interface MenuItemInputs extends Omit, 'index' | 'selectable'> { parent: SignalLike | MenuBarPattern | undefined>; - role: SignalLike<'menuitem' | 'menuitemradio' | 'menuitemcheckbox'>; submenu: SignalLike | undefined>; } // @public export class MenuItemPattern implements ListItem { constructor(inputs: MenuItemInputs); - readonly active: SignalLike; + active: SignalLike; close(opts?: { refocus?: boolean; }): void; - readonly controls: WritableSignalLike; - readonly disabled: () => boolean; - readonly element: SignalLike; - readonly expanded: SignalLike; - readonly _expanded: WritableSignalLike; - readonly hasBeenInteracted: WritableSignalLike; - readonly hasPopup: SignalLike; - readonly id: SignalLike; - readonly index: SignalLike; + controls: WritableSignalLike; + disabled: () => boolean; + element: SignalLike; + expanded: SignalLike; + _expanded: WritableSignalLike; + hasBeenFocused: WritableSignalLike; + hasPopup: SignalLike; + id: SignalLike; + index: SignalLike; // (undocumented) readonly inputs: MenuItemInputs; onFocusIn(): void; @@ -419,12 +567,12 @@ export class MenuItemPattern implements ListItem { first?: boolean; last?: boolean; }): void; - readonly role: () => "menuitem" | "menuitemradio" | "menuitemcheckbox"; - readonly searchTerm: SignalLike; - readonly selectable: SignalLike; - readonly submenu: SignalLike | undefined>; - readonly tabIndex: SignalLike<0 | -1>; - readonly value: SignalLike; + role: () => string; + searchTerm: SignalLike; + selectable: SignalLike; + submenu: SignalLike | undefined>; + tabIndex: SignalLike<0 | -1>; + value: SignalLike; } // @public @@ -437,19 +585,19 @@ export class MenuPattern { closeAll(): void; _closeTimeout: any; collapse(): void; - readonly disabled: () => boolean; - readonly dynamicSpaceKey: SignalLike<"" | " ">; + disabled: () => boolean; + dynamicSpaceKey: SignalLike<"" | " ">; expand(): void; first(): void; - readonly hasBeenHovered: WritableSignalLike; - readonly hasBeenInteracted: WritableSignalLike; - readonly id: SignalLike; + hasBeenFocused: WritableSignalLike; + hasBeenHovered: WritableSignalLike; + id: SignalLike; // (undocumented) readonly inputs: MenuInputs; - readonly isFocused: WritableSignalLike; - readonly keydownManager: SignalLike>; + isFocused: WritableSignalLike; + keydownManager: SignalLike>; last(): void; - readonly listBehavior: List, V>; + listBehavior: List, V>; next(): void; onClick(event: MouseEvent): void; onFocusIn(): void; @@ -459,17 +607,15 @@ export class MenuPattern { onMouseOver(event: MouseEvent): void; _openTimeout: any; prev(): void; - readonly role: () => string; - readonly root: SignalLike | MenuBarPattern | MenuPattern | undefined>; + role: () => string; + root: SignalLike | MenuBarPattern | MenuPattern | undefined>; setDefaultState(): void; - setDefaultStateEffect(): void; - readonly shouldFocus: SignalLike; + shouldFocus: SignalLike; submit(item?: MenuItemPattern | undefined): void; - readonly tabIndex: () => 0 | -1; + tabIndex: () => 0 | -1; trigger(): void; - readonly typeaheadRegexp: RegExp; - validate(): string[]; - readonly visible: SignalLike; + typeaheadRegexp: RegExp; + visible: SignalLike; } // @public @@ -486,14 +632,14 @@ export class MenuTriggerPattern { close(opts?: { refocus?: boolean; }): void; - readonly disabled: () => boolean; - readonly expanded: WritableSignalLike; - readonly hasBeenInteracted: WritableSignalLike; - readonly hasPopup: () => boolean; + disabled: () => boolean; + expanded: WritableSignalLike; + hasBeenFocused: WritableSignalLike; + hasPopup: () => boolean; // (undocumented) readonly inputs: MenuTriggerInputs; - readonly keydownManager: SignalLike>; - readonly menu: SignalLike | undefined>; + keydownManager: SignalLike>; + menu: SignalLike | undefined>; onClick(): void; onFocusIn(): void; onFocusOut(event: FocusEvent): void; @@ -502,10 +648,8 @@ export class MenuTriggerPattern { first?: boolean; last?: boolean; }): void; - readonly pendingFocus: WritableSignalLike<"first" | "last" | undefined>; - pendingFocusEffect(): void; - readonly role: () => string; - readonly tabIndex: SignalLike<-1 | 0>; + role: () => string; + tabIndex: SignalLike<-1 | 0>; } // @public @@ -517,25 +661,19 @@ export interface OptionInputs extends Omit, 'index' | 'selectable // @public export class OptionPattern { constructor(args: OptionInputs); - readonly active: SignalLike; - readonly disabled: SignalLike; - readonly element: SignalLike; - readonly id: SignalLike; - readonly index: SignalLike; - readonly listbox: SignalLike | undefined>; - readonly searchTerm: SignalLike; - readonly selectable: () => boolean; - readonly selected: SignalLike; - readonly tabIndex: SignalLike<0 | -1 | undefined>; - readonly value: SignalLike; + active: SignalLike; + disabled: SignalLike; + element: SignalLike; + id: SignalLike; + index: SignalLike; + listbox: SignalLike | undefined>; + searchTerm: SignalLike; + selectable: () => boolean; + selected: SignalLike; + tabIndex: SignalLike<0 | -1 | undefined>; + value: SignalLike; } -// @public -export function reportViolations(violations: string[], element: Element): void; - -// @public -export function resolveElement(resolver: ElementResolver, context: HTMLElement): T | undefined; - // @public (undocumented) export function signal(initialValue: T): WritableSignalLike; @@ -543,34 +681,14 @@ export function signal(initialValue: T): WritableSignalLike; export type SignalLike = () => T; // @public -export function sortDirectives(a: HasElement, b: HasElement): 1 | -1; - -// @public -export class SortedCollection { - // (undocumented) - readonly orderedItems: Signal; - // (undocumented) - register(item: T): void; - // (undocumented) - startObserving(element: HTMLElement): void; - // (undocumented) - stopObserving(): void; - // (undocumented) - unregister(item: T): void; -} - -// @public -export function tabIndexTransform(v: string | number | undefined): number | undefined; - -// @public -export interface TabInputs extends Omit, Omit { - tabList: SignalLike; - tabPanel: SignalLike; +export interface TabInputs extends Omit, Omit { + tablist: SignalLike; + tabpanel: SignalLike; + value: SignalLike; } // @public export interface TabListInputs extends Omit, 'multi'>, Omit { - selectedTab: WritableSignalLike; selectionMode: SignalLike<'follow' | 'explicit'>; } @@ -579,33 +697,32 @@ export class TabListPattern { constructor(inputs: TabListInputs); readonly activeDescendant: SignalLike; readonly activeTab: SignalLike; - readonly clickManager: SignalLike>; readonly disabled: SignalLike; readonly expansionBehavior: ListExpansion; readonly focusBehavior: ListFocus; readonly followFocus: SignalLike; - readonly hasBeenInteracted: WritableSignalLike; // (undocumented) readonly inputs: TabListInputs; readonly keydown: SignalLike>; readonly navigationBehavior: ListNavigation; readonly nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; - onClick(event: PointerEvent): void; - onFocusIn(): void; onKeydown(event: KeyboardEvent): void; + onPointerdown(event: PointerEvent): void; + open(value: string): boolean; open(tab?: TabPattern): boolean; readonly orientation: SignalLike<'vertical' | 'horizontal'>; + readonly pointerdown: SignalLike>; readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; readonly selectedTab: WritableSignalLike; setDefaultState(): void; - setDefaultStateEffect(): void; readonly tabIndex: SignalLike<0 | -1>; } // @public -export interface TabPanelInputs { +export interface TabPanelInputs extends LabelControlOptionalInputs { id: SignalLike; - readonly tab: SignalLike; + tab: SignalLike; + value: SignalLike; } // @public @@ -616,7 +733,9 @@ export class TabPanelPattern { // (undocumented) readonly inputs: TabPanelInputs; readonly labelledBy: SignalLike; + readonly labelManager: LabelControl; readonly tabIndex: SignalLike<-1 | 0>; + readonly value: SignalLike; } // @public @@ -627,14 +746,15 @@ export class TabPattern { readonly disabled: SignalLike; readonly element: SignalLike; readonly expandable: SignalLike; - // (undocumented) readonly expanded: WritableSignalLike; readonly id: SignalLike; + readonly index: SignalLike; // (undocumented) readonly inputs: TabInputs; open(): boolean; readonly selected: SignalLike; readonly tabIndex: SignalLike<0 | -1>; + readonly value: SignalLike; } // @public @@ -648,13 +768,10 @@ export class ToolbarPattern { readonly activeDescendant: SignalLike; readonly activeItem: () => ToolbarWidgetPattern | undefined; readonly disabled: SignalLike; - readonly hasBeenInteracted: WritableSignalLike; // (undocumented) readonly inputs: ToolbarInputs; readonly listBehavior: List, V>; onClick(event: MouseEvent): void; - // (undocumented) - onFocusIn(): void; onKeydown(event: KeyboardEvent): void; // (undocumented) onPointerdown(event: PointerEvent): void; @@ -662,10 +779,8 @@ export class ToolbarPattern { // (undocumented) select(): void; setDefaultState(): void; - setDefaultStateEffect(): void; readonly softDisabled: SignalLike; readonly tabIndex: SignalLike<0 | -1>; - validate(): string[]; } // @public @@ -767,7 +882,6 @@ export class TreePattern implements TreeInputs { readonly activeDescendant: SignalLike; readonly activeItem: WritableSignalLike | undefined>; readonly children: SignalLike[]>; - readonly clickManager: SignalLike>; readonly collapseKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; _collapseOrParent(opts?: SelectOptions): void; readonly currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; @@ -781,7 +895,6 @@ export class TreePattern implements TreeInputs { readonly followFocus: SignalLike; protected _getItem(event: Event): TreeItemPattern | undefined; goto(e: PointerEvent, opts?: SelectOptions): void; - readonly hasBeenInteracted: WritableSignalLike; readonly id: SignalLike; // (undocumented) readonly inputs: TreeInputs; @@ -792,14 +905,13 @@ export class TreePattern implements TreeInputs { readonly multi: SignalLike; readonly nav: SignalLike; readonly nextKey: SignalLike<"ArrowRight" | "ArrowLeft" | "ArrowDown">; - onClick(event: PointerEvent): void; - onFocusIn(): void; onKeydown(event: KeyboardEvent): void; + onPointerdown(event: PointerEvent): void; readonly orientation: SignalLike<'vertical' | 'horizontal'>; + pointerdown: SignalLike>; readonly prevKey: SignalLike<"ArrowUp" | "ArrowRight" | "ArrowLeft">; readonly selectionMode: SignalLike<'follow' | 'explicit'>; setDefaultState(): void; - setDefaultStateEffect(): void; readonly softDisabled: SignalLike; readonly tabIndex: SignalLike<-1 | 0>; readonly textDirection: SignalLike<'ltr' | 'rtl'>; @@ -807,7 +919,7 @@ export class TreePattern implements TreeInputs { readonly typeaheadDelay: SignalLike; readonly typeaheadRegexp: RegExp; validate(): string[]; - readonly value: WritableSignalLike; + readonly values: WritableSignalLike; readonly visible: () => boolean; readonly wrap: SignalLike; } diff --git a/goldens/aria/tabs/index.api.md b/goldens/aria/tabs/index.api.md index fdb9b12b2f05..1cdaf2286e95 100644 --- a/goldens/aria/tabs/index.api.md +++ b/goldens/aria/tabs/index.api.md @@ -8,12 +8,9 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; -import { Signal } from '@angular/core'; -import { WritableSignal } from '@angular/core'; // @public export class Tab implements HasElement, OnInit, OnDestroy { - constructor(); readonly active: _angular_core.Signal; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; @@ -43,25 +40,27 @@ export class TabContent { // @public export class TabList implements OnInit, OnDestroy { constructor(); - readonly _collection: SortedCollection; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; - // (undocumented) - findTab(value?: string): Tab | undefined; readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">; // (undocumented) ngOnDestroy(): void; // (undocumented) ngOnInit(): void; + // (undocumented) + _onFocus(): void; open(value: string): boolean; readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: TabListPattern; + // (undocumented) + _register(child: Tab): void; readonly selectedTab: _angular_core.ModelSignal; readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">; readonly softDisabled: _angular_core.InputSignalWithTransform; readonly _tabPatterns: _angular_core.Signal; - readonly _tabsParent: Tabs; - readonly textDirection: WritableSignal<_angular_cdk_bidi.Direction>; + readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; + // (undocumented) + _unregister(child: Tab): void; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration; @@ -82,27 +81,20 @@ export class TabPanel implements OnInit, OnDestroy { readonly value: _angular_core.InputSignal; readonly visible: _angular_core.Signal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } // @public -export class Tabs implements OnDestroy { - constructor(); - readonly _collection: SortedCollection; +export class Tabs { readonly element: HTMLElement; // (undocumented) - ngOnDestroy(): void; - readonly _panelMap: _angular_core.Signal>; - // (undocumented) - _register(child: TabList): void; - readonly _tabList: _angular_core.WritableSignal; - readonly _tabMap: _angular_core.Signal>; - readonly _tabPanelPatterns: _angular_core.Signal; + _register(child: TabList | TabPanel): void; readonly _tabPatterns: _angular_core.Signal; + readonly _unorderedTabpanelPatterns: _angular_core.Signal; // (undocumented) - _unregister(): void; + _unregister(child: TabList | TabPanel): void; // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/goldens/aria/tabs/testing/index.api.md b/goldens/aria/tabs/testing/index.api.md deleted file mode 100644 index c3da3cb64ae3..000000000000 --- a/goldens/aria/tabs/testing/index.api.md +++ /dev/null @@ -1,50 +0,0 @@ -## API Report File for "@angular/aria_tabs_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class TabHarness extends ContentContainerComponentHarness { - // (undocumented) - protected getRootHarnessLoader(): Promise; - getTitle(): Promise; - // (undocumented) - static hostSelector: string; - isActive(): Promise; - isDisabled(): Promise; - isSelected(): Promise; - select(): Promise; - static with(options?: TabHarnessFilters): HarnessPredicate; -} - -// @public -export interface TabHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - selected?: boolean; - title?: string | RegExp; -} - -// @public -export class TabsHarness extends ComponentHarness { - getSelectedTab(): Promise; - getTabs(filters?: TabHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - selectTab(filters?: TabHarnessFilters): Promise; - static with(options?: TabsHarnessFilters): HarnessPredicate; -} - -// @public -export interface TabsHarnessFilters extends BaseHarnessFilters { -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/toolbar/index.api.md b/goldens/aria/toolbar/index.api.md index 2d833a5cf11a..c43cad405838 100644 --- a/goldens/aria/toolbar/index.api.md +++ b/goldens/aria/toolbar/index.api.md @@ -8,25 +8,27 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; -import { Signal } from '@angular/core'; // @public -export class Toolbar implements OnDestroy { +export class Toolbar { constructor(); - readonly _collection: SortedCollection>; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly _itemPatterns: _angular_core.Signal[]>; // (undocumented) - ngOnDestroy(): void; + _onFocus(): void; readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: ToolbarPattern; - readonly softDisabled: _angular_core.InputSignalWithTransform; + // (undocumented) + _register(widget: ToolbarWidget): void; + softDisabled: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; - readonly value: _angular_core.ModelSignal; + // (undocumented) + _unregister(widget: ToolbarWidget): void; + readonly values: _angular_core.ModelSignal; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngToolbar]", ["ngToolbar"], { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngToolbar]", ["ngToolbar"], { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; }, never, never, true, never>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } @@ -55,7 +57,6 @@ export class ToolbarWidget implements OnInit, OnDestroy { // @public export class ToolbarWidgetGroup { - constructor(); readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; readonly multi: _angular_core.InputSignalWithTransform; diff --git a/goldens/aria/toolbar/testing/index.api.md b/goldens/aria/toolbar/testing/index.api.md deleted file mode 100644 index d8f7d8b0468f..000000000000 --- a/goldens/aria/toolbar/testing/index.api.md +++ /dev/null @@ -1,60 +0,0 @@ -## API Report File for "@angular/aria_toolbar_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public -export class ToolbarHarness extends ComponentHarness { - getOrientation(): Promise<'vertical' | 'horizontal'>; - getWidgetGroups(filters?: ToolbarWidgetGroupHarnessFilters): Promise; - getWidgets(filters?: ToolbarWidgetHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - isDisabled(): Promise; - static with(options?: ToolbarHarnessFilters): HarnessPredicate; -} - -// @public -export interface ToolbarHarnessFilters extends BaseHarnessFilters { -} - -// @public -export class ToolbarWidgetGroupHarness extends ComponentHarness { - getWidgets(filters?: ToolbarWidgetHarnessFilters): Promise; - // (undocumented) - static hostSelector: string; - static with(options?: ToolbarWidgetGroupHarnessFilters): HarnessPredicate; -} - -// @public -export interface ToolbarWidgetGroupHarnessFilters extends BaseHarnessFilters { -} - -// @public -export class ToolbarWidgetHarness extends ContentContainerComponentHarness { - click(): Promise; - getText(): Promise; - // (undocumented) - static hostSelector: string; - isActive(): Promise; - isDisabled(): Promise; - isSelected(): Promise; - static with(options?: ToolbarWidgetHarnessFilters): HarnessPredicate; -} - -// @public -export interface ToolbarWidgetHarnessFilters extends BaseHarnessFilters { - active?: boolean; - selected?: boolean; - text?: string | RegExp; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/aria/tree/index.api.md b/goldens/aria/tree/index.api.md index fdc2b867dead..e796fe0102d4 100644 --- a/goldens/aria/tree/index.api.md +++ b/goldens/aria/tree/index.api.md @@ -11,10 +11,8 @@ import { OnInit } from '@angular/core'; import { Signal } from '@angular/core'; // @public -export class Tree implements OnDestroy { +export class Tree { constructor(); - readonly activeDescendant: Signal; - readonly _collection: SortedCollection>; readonly currentType: _angular_core.InputSignal<"page" | "step" | "location" | "date" | "time" | "true" | "false">; readonly disabled: _angular_core.InputSignalWithTransform; readonly element: HTMLElement; @@ -23,20 +21,23 @@ export class Tree implements OnDestroy { readonly multi: _angular_core.InputSignalWithTransform; readonly nav: _angular_core.InputSignalWithTransform; // (undocumented) - ngOnDestroy(): void; + _onFocus(): void; readonly orientation: _angular_core.InputSignal<"vertical" | "horizontal">; readonly _pattern: TreePattern; // (undocumented) + _register(child: TreeItem): void; + // (undocumented) scrollActiveItemIntoView(options?: ScrollIntoViewOptions): void; readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">; readonly softDisabled: _angular_core.InputSignalWithTransform; - readonly tabIndex: _angular_core.InputSignalWithTransform; readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>; readonly typeaheadDelay: _angular_core.InputSignal; - readonly value: _angular_core.ModelSignal; + // (undocumented) + _unregister(child: TreeItem): void; + readonly values: _angular_core.ModelSignal; readonly wrap: _angular_core.InputSignalWithTransform; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "values": "valuesChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration, never>; } diff --git a/goldens/aria/tree/testing/index.api.md b/goldens/aria/tree/testing/index.api.md deleted file mode 100644 index 2c30612c9378..000000000000 --- a/goldens/aria/tree/testing/index.api.md +++ /dev/null @@ -1,61 +0,0 @@ -## API Report File for "@angular/aria_tree_testing" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; -import { HarnessPredicate } from '@angular/cdk/testing'; - -// @public (undocumented) -export interface TextTree { - // (undocumented) - children?: TextTree[]; - // (undocumented) - text?: string; -} - -// @public -export class TreeHarness extends ComponentHarness { - getItems(filter?: TreeItemHarnessFilters): Promise; - getTreeStructure(): Promise; - // (undocumented) - static hostSelector: string; - static with(options?: TreeHarnessFilters): HarnessPredicate; -} - -// @public -export interface TreeHarnessFilters extends BaseHarnessFilters { -} - -// @public -export class TreeItemHarness extends ContentContainerComponentHarness { - blur(): Promise; - click(): Promise; - focus(): Promise; - getLevel(): Promise; - getText(): Promise; - // (undocumented) - static hostSelector: string; - isActive(): Promise; - isDisabled(): Promise; - isExpanded(): Promise; - isFocused(): Promise; - isSelected(): Promise; - static with(options?: TreeItemHarnessFilters): HarnessPredicate; -} - -// @public -export interface TreeItemHarnessFilters extends BaseHarnessFilters { - disabled?: boolean; - expanded?: boolean; - level?: number; - selected?: boolean; - text?: string | RegExp; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/goldens/cdk/a11y/index.api.md b/goldens/cdk/a11y/index.api.md index 3459b6d58d38..55978ba40ffe 100644 --- a/goldens/cdk/a11y/index.api.md +++ b/goldens/cdk/a11y/index.api.md @@ -46,7 +46,7 @@ export function addAriaReferencedId(el: Element, attr: `aria-${string}`, id: str // @public export class AriaDescriber implements OnDestroy { - constructor(); + constructor(...args: unknown[]); describe(hostElement: Element, message: string, role?: string): void; describe(hostElement: Element, message: HTMLElement): void; ngOnDestroy(): void; @@ -61,9 +61,15 @@ export class AriaDescriber implements OnDestroy { // @public export type AriaLivePoliteness = 'off' | 'polite' | 'assertive'; +// @public @deprecated +export const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = "cdk-describedby-host"; + +// @public @deprecated +export const CDK_DESCRIBEDBY_ID_PREFIX = "cdk-describedby-message"; + // @public export class CdkAriaLive implements OnDestroy { - constructor(); + constructor(...args: unknown[]); duration: number; // (undocumented) ngOnDestroy(): void; @@ -77,6 +83,7 @@ export class CdkAriaLive implements OnDestroy { // @public export class CdkMonitorFocus implements AfterViewInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) readonly cdkFocusChange: EventEmitter; // (undocumented) @@ -93,7 +100,7 @@ export class CdkMonitorFocus implements AfterViewInit, OnDestroy { // @public export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoCheck { - constructor(); + constructor(...args: unknown[]); autoCapture: boolean; get enabled(): boolean; set enabled(value: boolean); @@ -107,7 +114,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC // (undocumented) ngDoCheck(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -118,7 +125,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC // @public export class ConfigurableFocusTrap extends FocusTrap implements ManagedFocusTrap { - constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, _focusTrapManager: FocusTrapManager, _inertStrategy: FocusTrapInertStrategy, config: ConfigurableFocusTrapConfig, injector: Injector); + constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, _focusTrapManager: FocusTrapManager, _inertStrategy: FocusTrapInertStrategy, config: ConfigurableFocusTrapConfig, injector?: Injector); destroy(): void; _disable(): void; _enable(): void; @@ -133,8 +140,10 @@ export interface ConfigurableFocusTrapConfig { // @public export class ConfigurableFocusTrapFactory { - constructor(); + constructor(...args: unknown[]); create(element: HTMLElement, config?: ConfigurableFocusTrapConfig): ConfigurableFocusTrap; + // @deprecated (undocumented) + create(element: HTMLElement, deferCaptureElements: boolean): ConfigurableFocusTrap; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) @@ -168,7 +177,7 @@ export class FocusKeyManager extends ListKeyManager { // @public export class FocusMonitor implements OnDestroy { - constructor(); + constructor(...args: unknown[]); protected _document: Document; focusVia(element: HTMLElement, origin: FocusOrigin, options?: FocusOptions_2): void; focusVia(element: ElementRef, origin: FocusOrigin, options?: FocusOptions_2): void; @@ -208,7 +217,8 @@ export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null; // @public export class FocusTrap { - constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, deferAnchors: boolean | undefined, _injector: Injector); + constructor(_element: HTMLElement, _checker: InteractivityChecker, _ngZone: NgZone, _document: Document, deferAnchors?: boolean, + _injector?: Injector | undefined); attachAnchors(): boolean; destroy(): void; // (undocumented) @@ -228,8 +238,7 @@ export class FocusTrap { focusLastTabbableElement(options?: FocusOptions): boolean; focusLastTabbableElementWhenReady(options?: FocusOptions): Promise; hasAttached(): boolean; - // (undocumented) - readonly _injector: Injector; + readonly _injector?: Injector | undefined; // (undocumented) readonly _ngZone: NgZone; // (undocumented) @@ -239,7 +248,7 @@ export class FocusTrap { // @public export class FocusTrapFactory { - constructor(); + constructor(...args: unknown[]); create(element: HTMLElement, deferCaptureElements?: boolean): FocusTrap; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; @@ -268,7 +277,7 @@ export enum HighContrastMode { // @public export class HighContrastModeDetector implements OnDestroy { - constructor(); + constructor(...args: unknown[]); _applyBodyHighContrastModeCssClasses(): void; getHighContrastMode(): HighContrastMode; // (undocumented) @@ -305,7 +314,7 @@ export type InputModality = 'keyboard' | 'mouse' | 'touch' | null; // @public export class InputModalityDetector implements OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly modalityChanged: Observable; readonly modalityDetected: Observable; get mostRecentModality(): InputModality; @@ -325,6 +334,7 @@ export interface InputModalityDetectorOptions { // @public export class InteractivityChecker { + constructor(...args: unknown[]); isDisabled(element: HTMLElement): boolean; isFocusable(element: HTMLElement, config?: IsFocusableConfig): boolean; isTabbable(element: HTMLElement): boolean; @@ -394,7 +404,7 @@ export const LIVE_ANNOUNCER_ELEMENT_TOKEN: InjectionToken; // @public (undocumented) export class LiveAnnouncer implements OnDestroy { - constructor(); + constructor(...args: unknown[]); announce(message: LiveAnnouncerMessage): Promise; announce(message: LiveAnnouncerMessage, politeness?: AriaLivePoliteness): Promise; announce(message: LiveAnnouncerMessage, duration?: number): Promise; @@ -417,6 +427,9 @@ export interface LiveAnnouncerDefaultOptions { // @public export type LiveAnnouncerMessage = string | SafeHtml; +// @public @deprecated +export const MESSAGES_CONTAINER_ID = "cdk-describedby-message-container"; + // @public @deprecated export const NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER: Provider; diff --git a/goldens/cdk/accordion/index.api.md b/goldens/cdk/accordion/index.api.md index 5e2c57f5bddd..1b1ed1b9af99 100644 --- a/goldens/cdk/accordion/index.api.md +++ b/goldens/cdk/accordion/index.api.md @@ -24,12 +24,14 @@ export class CdkAccordion implements OnDestroy, OnChanges { // (undocumented) static ngAcceptInputType_multi: unknown; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; openAll(): void; readonly _openCloseAllActions: Subject; - readonly _stateChanges: Subject>; + readonly _stateChanges: Subject<{ + [propName: string]: i0.SimpleChange; + }>; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -38,6 +40,7 @@ export class CdkAccordion implements OnDestroy, OnChanges { // @public export class CdkAccordionItem implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) accordion: CdkAccordion; close(): void; diff --git a/goldens/cdk/bidi/index.api.md b/goldens/cdk/bidi/index.api.md index 31e4318ca8a8..5ca0f0661766 100644 --- a/goldens/cdk/bidi/index.api.md +++ b/goldens/cdk/bidi/index.api.md @@ -46,7 +46,7 @@ export type Direction = 'ltr' | 'rtl'; // @public export class Directionality implements OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly change: EventEmitter; // (undocumented) ngOnDestroy(): void; diff --git a/goldens/cdk/clipboard/index.api.md b/goldens/cdk/clipboard/index.api.md index b1df9b4f5483..12d1d5eb1ca5 100644 --- a/goldens/cdk/clipboard/index.api.md +++ b/goldens/cdk/clipboard/index.api.md @@ -14,7 +14,7 @@ export const CDK_COPY_TO_CLIPBOARD_CONFIG: InjectionToken; copy(attempts?: number): void; @@ -34,6 +34,7 @@ export interface CdkCopyToClipboardConfig { // @public class Clipboard_2 { + constructor(...args: unknown[]); beginCopy(text: string): PendingCopy; copy(text: string): boolean; // (undocumented) diff --git a/goldens/cdk/collections/index.api.md b/goldens/cdk/collections/index.api.md index 6f7944b333c2..ea03069d3844 100644 --- a/goldens/cdk/collections/index.api.md +++ b/goldens/cdk/collections/index.api.md @@ -71,11 +71,6 @@ export interface SelectionChange { // @public export class SelectionModel { constructor(_multiple?: boolean, initiallySelectedValues?: T[], _emitChanges?: boolean, compareWith?: ((o1: T, o2: T) => boolean) | undefined); - readonly bulk: Readonly<{ - select: (values: T[]) => boolean; - deselect: (values: T[]) => boolean; - setSelection: (values: T[]) => boolean; - }>; readonly changed: Subject>; clear(flushEvent?: boolean): boolean; // (undocumented) diff --git a/goldens/cdk/dialog/index.api.md b/goldens/cdk/dialog/index.api.md index e5d03096cd90..3b63c30c02d7 100644 --- a/goldens/cdk/dialog/index.api.md +++ b/goldens/cdk/dialog/index.api.md @@ -7,10 +7,8 @@ import { AfterContentInit } from '@angular/core'; import { AfterViewInit } from '@angular/core'; import * as _angular_cdk_portal from '@angular/cdk/portal'; -import { Binding } from '@angular/core'; import { ChangeDetectorRef } from '@angular/core'; import { ComponentRef } from '@angular/core'; -import { DirectiveWithBindings } from '@angular/core'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EmbeddedViewRef } from '@angular/core'; @@ -41,7 +39,7 @@ export type AutoFocusTarget = 'dialog' | 'first-tabbable' | 'first-heading'; // @public export class CdkDialogContainer extends BasePortalOutlet implements DialogContainer, OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) _addAriaLabelledBy(id: string): void; _ariaLabelledByQueue: string[]; @@ -85,6 +83,7 @@ export const DEFAULT_DIALOG_CONFIG: InjectionToken; get afterOpened(): Subject>; closeAll(): void; @@ -121,7 +120,6 @@ export class DialogConfig, container: C) => StaticProvider[]); - restoreFocus?: RestoreFocusValue; + restoreFocus?: boolean | string | HTMLElement; role?: DialogRole; scrollStrategy?: ScrollStrategy; templateContext?: Record | (() => Record); @@ -196,9 +194,6 @@ export class DialogRef { // @public export type DialogRole = 'dialog' | 'alertdialog'; -// @public -export type RestoreFocusValue = boolean | string | HTMLElement; - // @public (undocumented) export function throwDialogContentAlreadyAttachedError(): void; diff --git a/goldens/cdk/drag-drop/index.api.md b/goldens/cdk/drag-drop/index.api.md index 3b20babae00d..ed37827ef231 100644 --- a/goldens/cdk/drag-drop/index.api.md +++ b/goldens/cdk/drag-drop/index.api.md @@ -41,11 +41,11 @@ export const CDK_DRAG_PREVIEW: InjectionToken>; export const CDK_DROP_LIST: InjectionToken>; // @public -export const CDK_DROP_LIST_GROUP: InjectionToken>>; +export const CDK_DROP_LIST_GROUP: InjectionToken>; // @public export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) _addHandle(handle: CdkDragHandle): void; boundaryElement: string | ElementRef | HTMLElement; @@ -76,7 +76,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { // (undocumented) ngAfterViewInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; previewClass: string | string[]; @@ -152,7 +152,7 @@ export interface CdkDragExit { // @public export class CdkDragHandle implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); get disabled(): boolean; set disabled(value: boolean); // (undocumented) @@ -190,7 +190,7 @@ export interface CdkDragMove { // @public export class CdkDragPlaceholder implements OnDestroy { - constructor(); + constructor(...args: unknown[]); data: T; // (undocumented) ngOnDestroy(): void; @@ -204,7 +204,7 @@ export class CdkDragPlaceholder implements OnDestroy { // @public export class CdkDragPreview implements OnDestroy { - constructor(); + constructor(...args: unknown[]); data: T; matchSize: boolean; // (undocumented) @@ -241,7 +241,7 @@ export interface CdkDragStart { // @public export class CdkDropList implements OnDestroy { - constructor(); + constructor(...args: unknown[]); addItem(item: CdkDrag): void; autoScrollDisabled: boolean; autoScrollStep: NumberInput; @@ -313,6 +313,7 @@ export type DragConstrainPosition = (userPointerPosition: Point, dragRef: DragRe // @public @deprecated export class DragDrop { + constructor(...args: unknown[]); // @deprecated createDrag(element: ElementRef | HTMLElement, config?: DragRefConfig): DragRef; // @deprecated @@ -363,6 +364,7 @@ export class DragDropModule { // @public export class DragDropRegistry implements OnDestroy { + constructor(...args: unknown[]); getDragDirectiveForNode(node: Node): CdkDrag | null; isDragging(drag: DragRef): boolean; // (undocumented) @@ -497,7 +499,7 @@ export class DropListRef { data: T; disabled: boolean; dispose(): void; - drop(item: DragRef, currentIndex: number, previousIndex: number, previousContainer: DropListRef, isPointerOverContainer: boolean, distance: Point, dropPoint: Point, event: MouseEvent | TouchEvent): void; + drop(item: DragRef, currentIndex: number, previousIndex: number, previousContainer: DropListRef, isPointerOverContainer: boolean, distance: Point, dropPoint: Point, event?: MouseEvent | TouchEvent): void; readonly dropped: Subject<{ item: DragRef; currentIndex: number; diff --git a/goldens/cdk/layout/index.api.md b/goldens/cdk/layout/index.api.md index ab1df4a45870..173e2673ad8a 100644 --- a/goldens/cdk/layout/index.api.md +++ b/goldens/cdk/layout/index.api.md @@ -10,6 +10,7 @@ import { OnDestroy } from '@angular/core'; // @public export class BreakpointObserver implements OnDestroy { + constructor(...args: unknown[]); isMatched(value: string | readonly string[]): boolean; ngOnDestroy(): void; observe(value: string | readonly string[]): Observable; @@ -57,7 +58,7 @@ export class LayoutModule { // @public export class MediaMatcher { - constructor(); + constructor(...args: unknown[]); matchMedia(query: string): MediaQueryList; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; diff --git a/goldens/cdk/menu/index.api.md b/goldens/cdk/menu/index.api.md index 9a4e965ef739..0bb19d86f117 100644 --- a/goldens/cdk/menu/index.api.md +++ b/goldens/cdk/menu/index.api.md @@ -5,9 +5,7 @@ ```ts import { AfterContentInit } from '@angular/core'; -import { Binding } from '@angular/core'; import { ComponentRef } from '@angular/core'; -import { DirectiveWithBindings } from '@angular/core'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EmbeddedViewRef } from '@angular/core'; @@ -218,7 +216,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD getMenu(): Menu | undefined; _handleClick(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; open(): void; @@ -357,13 +355,15 @@ export interface MenuStackItem { } // @public -export class MenuTracker { +class MenuTracker { update(trigger: CdkMenuTriggerBase): void; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) static ɵprov: i0.ɵɵInjectableDeclaration; } +export { MenuTracker as ContextMenuTracker } +export { MenuTracker } // @public export const PARENT_OR_NEW_INLINE_MENU_STACK_PROVIDER: (orientation: "vertical" | "horizontal") => { diff --git a/goldens/cdk/observers/index.api.md b/goldens/cdk/observers/index.api.md index 19c3895d5372..7d59da63abe7 100644 --- a/goldens/cdk/observers/index.api.md +++ b/goldens/cdk/observers/index.api.md @@ -13,6 +13,7 @@ import { OnDestroy } from '@angular/core'; // @public export class CdkObserveContent implements AfterContentInit, OnDestroy { + constructor(...args: unknown[]); get debounce(): number; set debounce(value: NumberInput); get disabled(): boolean; @@ -32,6 +33,7 @@ export class CdkObserveContent implements AfterContentInit, OnDestroy { // @public export class ContentObserver implements OnDestroy { + constructor(...args: unknown[]); // (undocumented) ngOnDestroy(): void; observe(element: Element): Observable; diff --git a/goldens/cdk/overlay/index.api.md b/goldens/cdk/overlay/index.api.md index 2a4176493bbe..d0acaf6c780b 100644 --- a/goldens/cdk/overlay/index.api.md +++ b/goldens/cdk/overlay/index.api.md @@ -5,9 +5,7 @@ ```ts import { AfterContentInit } from '@angular/core'; -import { Binding } from '@angular/core'; import { ComponentRef } from '@angular/core'; -import { DirectiveWithBindings } from '@angular/core'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EmbeddedViewRef } from '@angular/core'; @@ -45,7 +43,7 @@ export const CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG: InjectionToken; attachOverlay(): void; backdropClass: string | string[]; @@ -79,7 +77,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { // (undocumented) static ngAcceptInputType_push: unknown; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; get offsetX(): number; @@ -159,6 +157,7 @@ export interface CdkConnectedOverlayConfig { // @public export class CdkOverlayOrigin { + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) @@ -168,7 +167,8 @@ export class CdkOverlayOrigin { } // @public -export class CdkScrollable implements ScrollDispatcherTarget, OnInit, OnDestroy { +export class CdkScrollable implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) protected readonly _destroyed: Subject; // (undocumented) @@ -302,7 +302,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { withPopoverLocation(location: FlexibleOverlayPopoverLocation): this; withPositions(positions: ConnectedPosition[]): this; withPush(canPush?: boolean): this; - withScrollableContainers(scrollables: ScrollDispatcherTarget[]): this; + withScrollableContainers(scrollables: CdkScrollable[]): this; withTransformOriginOn(selector: string): this; withViewportMargin(margin: ViewportMargin): this; } @@ -321,6 +321,7 @@ export type FlexibleOverlayPopoverLocation = 'global' | 'inline' | { // @public export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy { + constructor(...args: unknown[]); // (undocumented) protected _createContainer(): void; getFullscreenElement(): Element; @@ -372,6 +373,7 @@ export interface OriginConnectionPosition { // @public export class Overlay { + constructor(...args: unknown[]); create(config?: OverlayConfig): OverlayRef; position(): OverlayPositionBuilder; // (undocumented) @@ -416,6 +418,7 @@ export interface OverlayConnectionPosition { // @public export class OverlayContainer implements OnDestroy { + constructor(...args: unknown[]); // (undocumented) protected _containerElement: HTMLElement | undefined; protected _createContainer(): void; @@ -437,6 +440,7 @@ export class OverlayContainer implements OnDestroy { // @public export interface OverlayDefaultConfig { + // (undocumented) usePopover?: boolean; } @@ -472,6 +476,7 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher { // @public export class OverlayPositionBuilder { + constructor(...args: unknown[]); flexibleConnectedTo(origin: FlexibleConnectedPositionStrategyOrigin): FlexibleConnectedPositionStrategy; global(): GlobalPositionStrategy; // (undocumented) @@ -561,14 +566,15 @@ export interface RepositionScrollStrategyConfig { // @public export class ScrollDispatcher implements OnDestroy { - ancestorScrolled(elementOrElementRef: ElementRef | HTMLElement, auditTimeInMs?: number): Observable; - deregister(target: ScrollDispatcherTarget): void; - getAncestorScrollContainers(elementOrElementRef: ElementRef | HTMLElement): ScrollDispatcherTarget[]; + constructor(...args: unknown[]); + ancestorScrolled(elementOrElementRef: ElementRef | HTMLElement, auditTimeInMs?: number): Observable; + deregister(scrollable: CdkScrollable): void; + getAncestorScrollContainers(elementOrElementRef: ElementRef | HTMLElement): CdkScrollable[]; // (undocumented) ngOnDestroy(): void; - register(target: ScrollDispatcherTarget): void; - readonly scrollContainers: Map; - scrolled(auditTimeInMs?: number): Observable; + register(scrollable: CdkScrollable): void; + scrollContainers: Map; + scrolled(auditTimeInMs?: number): Observable; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) @@ -597,6 +603,7 @@ export interface ScrollStrategy { // @public export class ScrollStrategyOptions { + constructor(...args: unknown[]); block: () => BlockScrollStrategy; close: (config?: CloseScrollStrategyConfig) => CloseScrollStrategy; noop: () => NoopScrollStrategy; @@ -632,7 +639,7 @@ export type ViewportMargin = number | { // @public export class ViewportRuler implements OnDestroy { - constructor(); + constructor(...args: unknown[]); change(throttleTime?: number): Observable; protected _document: Document; getViewportRect(): { diff --git a/goldens/cdk/platform/index.api.md b/goldens/cdk/platform/index.api.md index dcc2005103a3..21144f237ded 100644 --- a/goldens/cdk/platform/index.api.md +++ b/goldens/cdk/platform/index.api.md @@ -29,6 +29,7 @@ export function normalizePassiveListenerOptions(options: AddEventListenerOptions // @public export class Platform { + constructor(..._args: unknown[]); ANDROID: boolean; BLINK: boolean; EDGE: boolean; diff --git a/goldens/cdk/portal/index.api.md b/goldens/cdk/portal/index.api.md index 0d09a65c62f9..4df75c360a2c 100644 --- a/goldens/cdk/portal/index.api.md +++ b/goldens/cdk/portal/index.api.md @@ -5,9 +5,7 @@ ```ts import { ApplicationRef } from '@angular/core'; -import { Binding } from '@angular/core'; import { ComponentRef } from '@angular/core'; -import { DirectiveWithBindings } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EmbeddedViewRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; @@ -16,7 +14,6 @@ import { Injector } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { TemplateRef } from '@angular/core'; -import { Type } from '@angular/core'; import { ViewContainerRef } from '@angular/core'; // @public @@ -42,7 +39,7 @@ export abstract class BasePortalOutlet implements PortalOutlet { // @public export class CdkPortal extends TemplatePortal { - constructor(); + constructor(...args: unknown[]); // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -51,6 +48,7 @@ export class CdkPortal extends TemplatePortal { // @public export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestroy { + constructor(...args: unknown[]); attachComponentPortal(portal: ComponentPortal): ComponentRef; // @deprecated attachDomPortal: (portal: DomPortal) => void; @@ -74,10 +72,8 @@ export type CdkPortalOutletAttachedRef = ComponentRef | EmbeddedViewRef extends Portal> { - constructor(component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, projectableNodes?: Node[][] | null, bindings?: Binding[], directives?: (Type | DirectiveWithBindings)[]); - readonly bindings: Binding[] | null; + constructor(component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, projectableNodes?: Node[][] | null); component: ComponentType; - readonly directives: (Type | DirectiveWithBindings)[] | null; injector?: Injector | null; projectableNodes?: Node[][] | null; viewContainerRef?: ViewContainerRef | null; diff --git a/goldens/cdk/scrolling/index.api.md b/goldens/cdk/scrolling/index.api.md index fc7f4f015fe4..81b2cde9f891 100644 --- a/goldens/cdk/scrolling/index.api.md +++ b/goldens/cdk/scrolling/index.api.md @@ -53,7 +53,8 @@ export class CdkFixedSizeVirtualScroll implements OnChanges { } // @public -export class CdkScrollable implements ScrollDispatcherTarget, OnInit, OnDestroy { +export class CdkScrollable implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) protected readonly _destroyed: Subject; // (undocumented) @@ -92,7 +93,7 @@ export class CdkScrollableModule { // @public export class CdkVirtualForOf implements CdkVirtualScrollRepeater, CollectionViewer, DoCheck, OnDestroy { - constructor(); + constructor(...args: unknown[]); get cdkVirtualForOf(): DataSource | Observable | NgIterable | null | undefined; set cdkVirtualForOf(value: DataSource | Observable | NgIterable | null | undefined); // (undocumented) @@ -131,6 +132,7 @@ export type CdkVirtualForOfContext = { // @public export abstract class CdkVirtualScrollable extends CdkScrollable { + constructor(...args: unknown[]); abstract measureBoundingClientRectWithScrollOffset(from: 'left' | 'top' | 'right' | 'bottom'): number; measureViewportSize(orientation: 'horizontal' | 'vertical'): number; // (undocumented) @@ -141,6 +143,7 @@ export abstract class CdkVirtualScrollable extends CdkScrollable { // @public export class CdkVirtualScrollableElement extends CdkVirtualScrollable { + constructor(...args: unknown[]); // (undocumented) measureBoundingClientRectWithScrollOffset(from: 'left' | 'top' | 'right' | 'bottom'): number; // (undocumented) @@ -151,7 +154,7 @@ export class CdkVirtualScrollableElement extends CdkVirtualScrollable { // @public export class CdkVirtualScrollableWindow extends CdkVirtualScrollable { - constructor(); + constructor(...args: unknown[]); // (undocumented) measureBoundingClientRectWithScrollOffset(from: 'left' | 'top' | 'right' | 'bottom'): number; // (undocumented) @@ -170,7 +173,7 @@ export interface CdkVirtualScrollRepeater { // @public export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements OnInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); appendOnly: boolean; attach(forOf: CdkVirtualScrollRepeater): void; checkViewportSize(): void; @@ -257,26 +260,21 @@ export type _Right = { // @public export class ScrollDispatcher implements OnDestroy { - ancestorScrolled(elementOrElementRef: ElementRef | HTMLElement, auditTimeInMs?: number): Observable; - deregister(target: ScrollDispatcherTarget): void; - getAncestorScrollContainers(elementOrElementRef: ElementRef | HTMLElement): ScrollDispatcherTarget[]; + constructor(...args: unknown[]); + ancestorScrolled(elementOrElementRef: ElementRef | HTMLElement, auditTimeInMs?: number): Observable; + deregister(scrollable: CdkScrollable): void; + getAncestorScrollContainers(elementOrElementRef: ElementRef | HTMLElement): CdkScrollable[]; // (undocumented) ngOnDestroy(): void; - register(target: ScrollDispatcherTarget): void; - readonly scrollContainers: Map; - scrolled(auditTimeInMs?: number): Observable; + register(scrollable: CdkScrollable): void; + scrollContainers: Map; + scrolled(auditTimeInMs?: number): Observable; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) static ɵprov: i0.ɵɵInjectableDeclaration; } -// @public -export interface ScrollDispatcherTarget { - elementScrolled(): Observable; - getElementRef(): ElementRef; -} - // @public export class ScrollingModule { // (undocumented) @@ -299,7 +297,7 @@ export type _Top = { // @public export class ViewportRuler implements OnDestroy { - constructor(); + constructor(...args: unknown[]); change(throttleTime?: number): Observable; protected _document: Document; getViewportRect(): { diff --git a/goldens/cdk/stepper/index.api.md b/goldens/cdk/stepper/index.api.md index 2136b8b1117d..a26d32c85c00 100644 --- a/goldens/cdk/stepper/index.api.md +++ b/goldens/cdk/stepper/index.api.md @@ -9,7 +9,6 @@ import { AfterContentInit } from '@angular/core'; import { AfterViewInit } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; import { InjectionToken } from '@angular/core'; @@ -22,7 +21,7 @@ import { TemplateRef } from '@angular/core'; // @public (undocumented) export class CdkStep implements OnChanges { - constructor(); + constructor(...args: unknown[]); ariaLabel: string; ariaLabelledby: string; protected _childForms: QueryList> | undefined; @@ -64,7 +63,7 @@ export class CdkStep implements OnChanges { _showError(): boolean; get state(): StepState; set state(value: StepState); - stepControl: StepControl; + stepControl: AbstractControl; stepLabel: CdkStepLabel; // (undocumented) _stepper: CdkStepper; @@ -76,6 +75,7 @@ export class CdkStep implements OnChanges { // @public (undocumented) export class CdkStepHeader implements FocusableOption { + constructor(...args: unknown[]); // (undocumented) _elementRef: ElementRef; focus(): void; @@ -87,6 +87,7 @@ export class CdkStepHeader implements FocusableOption { // @public (undocumented) export class CdkStepLabel { + constructor(...args: unknown[]); // (undocumented) template: TemplateRef; // (undocumented) @@ -97,6 +98,7 @@ export class CdkStepLabel { // @public (undocumented) export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { + constructor(...args: unknown[]); protected readonly _destroyed: Subject; // (undocumented) protected _elementRef: ElementRef; @@ -104,8 +106,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { _getFocusIndex(): number | null; _getStepContentId(i: number): string; _getStepLabelId(i: number): string; - get linear(): boolean; - set linear(value: boolean); + linear: boolean; next(): void; // (undocumented) static ngAcceptInputType_linear: unknown; @@ -151,6 +152,7 @@ export class CdkStepperModule { // @public export class CdkStepperNext { + constructor(...args: unknown[]); // (undocumented) _stepper: CdkStepper; type: string; @@ -162,6 +164,7 @@ export class CdkStepperNext { // @public export class CdkStepperPrevious { + constructor(...args: unknown[]); // (undocumented) _stepper: CdkStepper; type: string; @@ -182,9 +185,6 @@ export const STEP_STATE: { // @public export type StepContentPositionState = 'previous' | 'current' | 'next'; -// @public -export type StepControl = AbstractControl | Field; - // @public export const STEPPER_GLOBAL_OPTIONS: InjectionToken; diff --git a/goldens/cdk/table/index.api.md b/goldens/cdk/table/index.api.md index 4a6d4cba4fa3..e78862330054 100644 --- a/goldens/cdk/table/index.api.md +++ b/goldens/cdk/table/index.api.md @@ -38,6 +38,7 @@ export class BaseCdkCell { // @public export abstract class BaseRowDef implements OnChanges { + constructor(...args: unknown[]); columns: Iterable; protected _columnsDiffer: IterableDiffer; // (undocumented) @@ -45,7 +46,7 @@ export abstract class BaseRowDef implements OnChanges { extractCellTemplate(column: CdkColumnDef): TemplateRef; getColumnsDiff(): IterableChanges | null; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) template: TemplateRef; // (undocumented) @@ -62,7 +63,7 @@ export const CDK_TABLE: InjectionToken; // @public export class CdkCell extends BaseCdkCell { - constructor(); + constructor(...args: unknown[]); // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -71,6 +72,7 @@ export class CdkCell extends BaseCdkCell { // @public export class CdkCellDef implements CellDef { + constructor(...args: unknown[]); template: TemplateRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; @@ -80,7 +82,7 @@ export class CdkCellDef implements CellDef { // @public export class CdkCellOutlet implements OnDestroy { - constructor(); + constructor(...args: unknown[]); cells: CdkCellDef[]; context: any; static mostRecentCellOutlet: CdkCellOutlet | null; @@ -119,6 +121,7 @@ export interface CdkCellOutletRowContext { // @public export class CdkColumnDef implements CanStick { + constructor(...args: unknown[]); cell: CdkCellDef; _columnCssClassName: string[]; cssClassFriendlyName: string; @@ -152,7 +155,7 @@ export class CdkColumnDef implements CanStick { // @public export class CdkFooterCell extends BaseCdkCell { - constructor(); + constructor(...args: unknown[]); // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -161,6 +164,7 @@ export class CdkFooterCell extends BaseCdkCell { // @public export class CdkFooterCellDef implements CellDef { + constructor(...args: unknown[]); template: TemplateRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; @@ -178,11 +182,12 @@ export class CdkFooterRow { // @public export class CdkFooterRowDef extends BaseRowDef implements CanStick, OnChanges { + constructor(...args: unknown[]); hasStickyChanged(): boolean; // (undocumented) static ngAcceptInputType_sticky: unknown; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; resetStickyChanged(): void; get sticky(): boolean; set sticky(value: boolean); @@ -196,7 +201,7 @@ export class CdkFooterRowDef extends BaseRowDef implements CanStick, OnChanges { // @public export class CdkHeaderCell extends BaseCdkCell { - constructor(); + constructor(...args: unknown[]); // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -205,6 +210,7 @@ export class CdkHeaderCell extends BaseCdkCell { // @public export class CdkHeaderCellDef implements CellDef { + constructor(...args: unknown[]); template: TemplateRef; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; @@ -222,11 +228,12 @@ export class CdkHeaderRow { // @public export class CdkHeaderRowDef extends BaseRowDef implements CanStick, OnChanges { + constructor(...args: unknown[]); hasStickyChanged(): boolean; // (undocumented) static ngAcceptInputType_sticky: unknown; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; resetStickyChanged(): void; get sticky(): boolean; set sticky(value: boolean); @@ -240,6 +247,7 @@ export class CdkHeaderRowDef extends BaseRowDef implements CanStick, OnChanges { // @public export class CdkNoDataRow { + constructor(...args: unknown[]); // (undocumented) _cellClassNames: string[]; // (undocumented) @@ -272,6 +280,7 @@ export class CdkRow { // @public export class CdkRowDef extends BaseRowDef { + constructor(...args: unknown[]); // (undocumented) _table?: any; when: (index: number, rowData: T) => boolean; @@ -283,7 +292,7 @@ export class CdkRowDef extends BaseRowDef { // @public export class CdkTable implements AfterContentInit, AfterContentChecked, CollectionViewer, OnDestroy, OnInit, StickyPositioningListener { - constructor(); + constructor(...args: unknown[]); addColumnDef(columnDef: CdkColumnDef): void; addFooterRowDef(footerRowDef: CdkFooterRowDef): void; addHeaderRowDef(headerRowDef: CdkHeaderRowDef): void; @@ -346,7 +355,6 @@ export class CdkTable implements AfterContentInit, AfterContentChecked, Colle removeHeaderRowDef(headerRowDef: CdkHeaderRowDef): void; removeRowDef(rowDef: CdkRowDef): void; protected _renderedRange?: ListRange; - get renderedRows(): readonly RenderRow[]; renderRows(): void; // (undocumented) _rowOutlet: DataRowOutlet; @@ -385,7 +393,7 @@ export class CdkTableModule { // @public export class CdkTextColumn implements OnDestroy, OnInit { - constructor(); + constructor(...args: unknown[]); cell: CdkCellDef; columnDef: CdkColumnDef; _createDefaultHeaderText(): string; @@ -415,7 +423,7 @@ export interface CellDef { // @public export class DataRowOutlet implements RowOutlet { - constructor(); + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) @@ -434,7 +442,7 @@ export abstract class DataSource { // @public export class FooterRowOutlet implements RowOutlet { - constructor(); + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) @@ -447,7 +455,7 @@ export class FooterRowOutlet implements RowOutlet { // @public export class HeaderRowOutlet implements RowOutlet { - constructor(); + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) @@ -460,7 +468,7 @@ export class HeaderRowOutlet implements RowOutlet { // @public export class NoDataRowOutlet implements RowOutlet { - constructor(); + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) diff --git a/goldens/cdk/text-field/index.api.md b/goldens/cdk/text-field/index.api.md index d8cc224be674..b9ab33d29cf3 100644 --- a/goldens/cdk/text-field/index.api.md +++ b/goldens/cdk/text-field/index.api.md @@ -21,6 +21,7 @@ export type AutofillEvent = { // @public export class AutofillMonitor implements OnDestroy { + constructor(...args: unknown[]); monitor(element: Element): Observable; monitor(element: ElementRef): Observable; // (undocumented) @@ -35,6 +36,7 @@ export class AutofillMonitor implements OnDestroy { // @public export class CdkAutofill implements OnDestroy, OnInit { + constructor(...args: unknown[]); readonly cdkAutofill: EventEmitter; // (undocumented) ngOnDestroy(): void; @@ -48,7 +50,7 @@ export class CdkAutofill implements OnDestroy, OnInit { // @public export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { - constructor(); + constructor(...args: unknown[]); protected _document: Document; get enabled(): boolean; set enabled(value: boolean); diff --git a/goldens/cdk/tree/index.api.md b/goldens/cdk/tree/index.api.md index 42dbb07f3828..9698b0bc6529 100644 --- a/goldens/cdk/tree/index.api.md +++ b/goldens/cdk/tree/index.api.md @@ -50,6 +50,7 @@ export const CDK_TREE_NODE_OUTLET_NODE: InjectionToken<{}>; // @public export class CdkNestedTreeNode extends CdkTreeNode implements AfterContentInit, OnDestroy { + constructor(...args: unknown[]); protected _children: T[]; protected _clear(): void; // (undocumented) @@ -70,6 +71,7 @@ export class CdkNestedTreeNode extends CdkTreeNode implements Af // @public export class CdkTree implements AfterContentChecked, AfterContentInit, AfterViewInit, CollectionViewer, OnDestroy, OnInit { + constructor(...args: unknown[]); childrenAccessor?: (dataNode: T) => T[] | Observable; collapse(dataNode: T): void; collapseAll(): void; @@ -140,7 +142,7 @@ export class CdkTreeModule { // @public export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerItem { - constructor(); + constructor(...args: unknown[]); activate(): void; readonly activation: EventEmitter; collapse(): void; @@ -210,6 +212,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI // @public export class CdkTreeNodeDef { + constructor(...args: unknown[]); template: TemplateRef; when: (index: number, nodeData: T) => boolean; // (undocumented) @@ -220,6 +223,7 @@ export class CdkTreeNodeDef { // @public export class CdkTreeNodeOutlet { + constructor(...args: unknown[]); // (undocumented) _node?: {} | null | undefined; // (undocumented) @@ -241,7 +245,7 @@ export class CdkTreeNodeOutletContext { // @public export class CdkTreeNodePadding implements OnDestroy { - constructor(); + constructor(...args: unknown[]); get indent(): number | string; set indent(indent: number | string); // (undocumented) @@ -268,11 +272,12 @@ export class CdkTreeNodePadding implements OnDestroy { // @public export class CdkTreeNodeToggle { + constructor(...args: unknown[]); // (undocumented) static ngAcceptInputType_recursive: unknown; recursive: boolean; // (undocumented) - _toggle(event: Event): void; + _toggle(): void; // (undocumented) protected _tree: CdkTree; // (undocumented) diff --git a/goldens/google-maps/index.api.md b/goldens/google-maps/index.api.md index a3fbc083cb41..21729fea202c 100644 --- a/goldens/google-maps/index.api.md +++ b/goldens/google-maps/index.api.md @@ -19,7 +19,7 @@ import { SimpleChanges } from '@angular/core'; // @public (undocumented) interface Algorithm_2 { - calculate: (input: AlgorithmInput) => AlgorithmOutput; + calculate: ({ markers, map }: AlgorithmInput) => AlgorithmOutput; } export { Algorithm_2 as Algorithm } @@ -131,6 +131,7 @@ export const defaultOnClusterClickHandler: onClusterClickHandler; // @public @deprecated export class DeprecatedMapMarkerClusterer implements OnInit, AfterContentInit, OnChanges, OnDestroy { + constructor(...args: unknown[]); // (undocumented) ariaLabelFn: AriaLabelFn; // (undocumented) @@ -209,7 +210,7 @@ export class DeprecatedMapMarkerClusterer implements OnInit, AfterContentInit, O // (undocumented) ngAfterContentInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -232,7 +233,7 @@ export class DeprecatedMapMarkerClusterer implements OnInit, AfterContentInit, O // @public export class GoogleMap implements OnChanges, OnInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly authFailure: EventEmitter; readonly boundsChanged: Observable; // (undocumented) @@ -270,7 +271,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { readonly maptypeidChanged: Observable; get mapTypes(): google.maps.MapTypeRegistry; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -305,17 +306,16 @@ export class GoogleMapsModule { static ɵmod: i0.ɵɵNgModuleDeclaration; } -// @public @deprecated -export type HeatmapData = any; +// @public +export type HeatmapData = google.maps.MVCArray | (google.maps.LatLng | google.maps.visualization.WeightedLocation | google.maps.LatLngLiteral)[]; // @public export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, MarkerDirective { + constructor(...args: unknown[]); advancedMarker: google.maps.marker.AdvancedMarkerElement; set content(content: Node | google.maps.marker.PinElement | null); // (undocumented) getAnchor(): google.maps.marker.AdvancedMarkerElement; - readonly gmpClick: Observable; - set gmpClickable(clickable: boolean); set gmpDraggable(draggable: boolean); readonly mapClick: Observable; readonly mapDblclick: Observable; @@ -328,7 +328,7 @@ export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAncho readonly mapRightclick: Observable; readonly markerInitialized: EventEmitter; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -339,7 +339,7 @@ export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAncho set title(title: string); set zIndex(zIndex: number); // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -352,6 +352,7 @@ export interface MapAnchorPoint { // @public (undocumented) export class MapBaseLayer implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) protected _initializeObject(): void; // (undocumented) @@ -388,6 +389,7 @@ export class MapBicyclingLayer implements OnInit, OnDestroy { // @public export class MapCircle implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) set center(center: google.maps.LatLng | google.maps.LatLngLiteral); // (undocumented) @@ -446,6 +448,7 @@ export class MapCircle implements OnInit, OnDestroy { // @public export class MapDirectionsRenderer implements OnInit, OnChanges, OnDestroy { + constructor(...args: unknown[]); set directions(directions: google.maps.DirectionsResult); readonly directionsChanged: Observable; directionsRenderer?: google.maps.DirectionsRenderer; @@ -454,7 +457,7 @@ export class MapDirectionsRenderer implements OnInit, OnChanges, OnDestroy { getPanel(): Node | null; getRouteIndex(): number; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -476,6 +479,7 @@ export interface MapDirectionsResponse { // @public export class MapDirectionsService { + constructor(...args: unknown[]); route(request: google.maps.DirectionsRequest): Observable; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; @@ -493,6 +497,7 @@ export class MapEventManager { // @public export class MapGeocoder { + constructor(...args: unknown[]); geocode(request: google.maps.GeocoderRequest): Observable; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; @@ -510,6 +515,7 @@ export interface MapGeocoderResponse { // @public export class MapGroundOverlay implements OnInit, OnDestroy { + constructor(...args: unknown[]); get bounds(): google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral; set bounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral); clickable: boolean; @@ -532,14 +538,20 @@ export class MapGroundOverlay implements OnInit, OnDestroy { static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public @deprecated -export class MapHeatmapLayer { - constructor(); +// @public +export class MapHeatmapLayer implements OnInit, OnChanges, OnDestroy { + constructor(...args: unknown[]); set data(data: HeatmapData); getData(): HeatmapData; - heatmap?: any; - readonly heatmapInitialized: EventEmitter; - set options(options: any); + heatmap?: google.maps.visualization.HeatmapLayer; + readonly heatmapInitialized: EventEmitter; + // (undocumented) + ngOnChanges(changes: SimpleChanges): void; + // (undocumented) + ngOnDestroy(): void; + // (undocumented) + ngOnInit(): void; + set options(options: Partial); // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) @@ -548,6 +560,7 @@ export class MapHeatmapLayer { // @public export class MapInfoWindow implements OnInit, OnDestroy { + constructor(...args: unknown[]); close(): void; readonly closeclick: Observable; readonly contentChanged: Observable; @@ -578,6 +591,7 @@ export class MapInfoWindow implements OnInit, OnDestroy { // @public export class MapKmlLayer implements OnInit, OnDestroy { + constructor(...args: unknown[]); readonly defaultviewportChanged: Observable; getDefaultViewport(): google.maps.LatLngBounds | null; getMetadata(): google.maps.KmlLayerMetadata | null; @@ -604,6 +618,7 @@ export class MapKmlLayer implements OnInit, OnDestroy { // @public export class MapMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, MarkerDirective { + constructor(...args: unknown[]); readonly animationChanged: Observable; set clickable(clickable: boolean); readonly clickableChanged: Observable; @@ -639,7 +654,7 @@ export class MapMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, marker?: google.maps.Marker; readonly markerInitialized: EventEmitter; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -671,7 +686,7 @@ export class MapMarkerClusterer implements OnInit, OnChanges, OnDestroy { // (undocumented) _markers: QueryList; // (undocumented) - ngOnChanges(changes: SimpleChanges): Promise; + ngOnChanges(changes: SimpleChanges): Promise; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -685,6 +700,7 @@ export class MapMarkerClusterer implements OnInit, OnChanges, OnDestroy { // @public export class MapPolygon implements OnInit, OnDestroy { + constructor(...args: unknown[]); getDraggable(): boolean; getEditable(): boolean; getPath(): google.maps.MVCArray; @@ -719,6 +735,7 @@ export class MapPolygon implements OnInit, OnDestroy { // @public export class MapPolyline implements OnInit, OnDestroy { + constructor(...args: unknown[]); getDraggable(): boolean; getEditable(): boolean; getPath(): google.maps.MVCArray; @@ -752,6 +769,7 @@ export class MapPolyline implements OnInit, OnDestroy { // @public export class MapRectangle implements OnInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) set bounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral); readonly boundsChanged: Observable; @@ -786,6 +804,7 @@ export class MapRectangle implements OnInit, OnDestroy { // @public export class MapTrafficLayer implements OnInit, OnDestroy { + constructor(...args: unknown[]); set autoRefresh(autoRefresh: boolean); // (undocumented) ngOnDestroy(): void; @@ -815,7 +834,7 @@ export class MapTransitLayer implements OnInit, OnDestroy { // @public (undocumented) export class MarkerClusterer extends google.maps.OverlayView { - constructor(input: MarkerClustererOptions_2); + constructor({ map, markers, algorithmOptions, algorithm, renderer, onClusterClick, }: MarkerClustererOptions_2); // (undocumented) addMarker(marker: Marker, noDraw?: boolean): void; // (undocumented) diff --git a/goldens/material/autocomplete/index.api.md b/goldens/material/autocomplete/index.api.md index f4b99c0778cf..d1d794adcb43 100644 --- a/goldens/material/autocomplete/index.api.md +++ b/goldens/material/autocomplete/index.api.md @@ -43,7 +43,7 @@ export const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any; // @public export class MatAutocomplete implements AfterContentInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected _animationsDisabled: boolean; ariaLabel: string; @@ -136,6 +136,7 @@ export class MatAutocompleteModule { // @public export class MatAutocompleteOrigin { + constructor(...args: unknown[]); // (undocumented) elementRef: ElementRef; // (undocumented) @@ -155,6 +156,7 @@ export class MatAutocompleteSelectedEvent { // @public export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy { + constructor(...args: unknown[]); get activeOption(): MatOption | null; autocomplete: MatAutocomplete; autocompleteAttribute: string; @@ -174,7 +176,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn // (undocumented) ngAfterViewInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; _onChange: (value: any) => void; @@ -202,7 +204,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn // @public export class MatOptgroup { - constructor(); + constructor(...args: unknown[]); disabled: boolean; _inert: boolean; label: string; @@ -217,7 +219,7 @@ export class MatOptgroup { // @public export class MatOption implements FocusableOption, AfterViewChecked, OnDestroy { - constructor(); + constructor(...args: unknown[]); get active(): boolean; // (undocumented) _changeDetectorRef: ChangeDetectorRef; diff --git a/goldens/material/badge/index.api.md b/goldens/material/badge/index.api.md index da5471d1382f..69f6b8665104 100644 --- a/goldens/material/badge/index.api.md +++ b/goldens/material/badge/index.api.md @@ -8,16 +8,12 @@ import { AfterViewInit } from '@angular/core'; import * as i0 from '@angular/core'; import * as i1 from '@angular/cdk/a11y'; import * as i2 from '@angular/cdk/bidi'; -import { InjectionToken } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; -// @public -export const MAT_BADGE_CONFIG: InjectionToken; - // @public export class MatBadge implements OnInit, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); get color(): ThemePalette; set color(value: ThemePalette); get content(): string | number | undefined | null; @@ -50,14 +46,6 @@ export class MatBadge implements OnInit, AfterViewInit, OnDestroy { static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public -export interface MatBadgeConfig { - color?: ThemePalette; - overlap?: boolean; - position?: MatBadgePosition; - size?: MatBadgeSize; -} - // @public (undocumented) export class MatBadgeModule { // (undocumented) diff --git a/goldens/material/bottom-sheet/index.api.md b/goldens/material/bottom-sheet/index.api.md index 1e99cde0cc4f..94225e8a2317 100644 --- a/goldens/material/bottom-sheet/index.api.md +++ b/goldens/material/bottom-sheet/index.api.md @@ -4,7 +4,6 @@ ```ts -import { Binding } from '@angular/core'; import { CdkDialogContainer } from '@angular/cdk/dialog'; import { ComponentRef } from '@angular/core'; import { ComponentType } from '@angular/cdk/portal'; @@ -19,7 +18,6 @@ import { InjectionToken } from '@angular/core'; import { Injector } from '@angular/core'; import { Observable } from 'rxjs'; import { OnDestroy } from '@angular/core'; -import { RestoreFocusValue } from '@angular/cdk/dialog'; import { ScrollStrategy } from '@angular/cdk/overlay'; import { TemplateRef } from '@angular/core'; import { ViewContainerRef } from '@angular/core'; @@ -35,6 +33,7 @@ export const MAT_BOTTOM_SHEET_DEFAULT_OPTIONS: InjectionToken(result?: R): void; // (undocumented) ngOnDestroy(): void; @@ -54,7 +53,6 @@ export class MatBottomSheetConfig { ariaModal?: boolean; autoFocus?: AutoFocusTarget | string | boolean; backdropClass?: string; - bindings?: Binding[]; closeOnNavigation?: boolean; data?: D | null; direction?: Direction; @@ -65,14 +63,14 @@ export class MatBottomSheetConfig { maxHeight?: number | string; minHeight?: number | string; panelClass?: string | string[]; - restoreFocus?: RestoreFocusValue; + restoreFocus?: boolean; scrollStrategy?: ScrollStrategy; viewContainerRef?: ViewContainerRef; } // @public export class MatBottomSheetContainer extends CdkDialogContainer implements OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected _animationsDisabled: boolean; _animationState: 'void' | 'visible' | 'hidden'; @@ -83,7 +81,7 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes enter(): void; exit(): void; // (undocumented) - protected _handleAnimationEvent(isStart: boolean, animationName: string, target: EventTarget | null): void; + protected _handleAnimationEvent(isStart: boolean, animationName: string): void; // (undocumented) ngOnDestroy(): void; // (undocumented) diff --git a/goldens/material/button-toggle/index.api.md b/goldens/material/button-toggle/index.api.md index e50988be35d0..80118072368e 100644 --- a/goldens/material/button-toggle/index.api.md +++ b/goldens/material/button-toggle/index.api.md @@ -28,7 +28,7 @@ export const MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR: any; // @public export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); get appearance(): MatButtonToggleAppearance; set appearance(value: MatButtonToggleAppearance); ariaLabel: string; @@ -96,7 +96,7 @@ export interface MatButtonToggleDefaultOptions { // @public export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, AfterContentInit { - constructor(); + constructor(...args: unknown[]); appearance: MatButtonToggleAppearance; _buttonToggles: QueryList; readonly change: EventEmitter; diff --git a/goldens/material/button/index.api.md b/goldens/material/button/index.api.md index ee1a2044088d..e5854c39659e 100644 --- a/goldens/material/button/index.api.md +++ b/goldens/material/button/index.api.md @@ -28,12 +28,12 @@ export type MatAnchor = MatButton; // @public export class MatButton extends MatButtonBase { - constructor(); + constructor(...args: unknown[]); get appearance(): MatButtonAppearance | null; set appearance(value: MatButtonAppearance | ''); setAppearance(appearance: MatButtonAppearance): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -66,7 +66,7 @@ export type MatFabAnchor = MatFabButton; // @public export class MatFabButton extends MatButtonBase { - constructor(); + constructor(...args: unknown[]); // (undocumented) extended: boolean; // (undocumented) @@ -74,7 +74,7 @@ export class MatFabButton extends MatButtonBase { // (undocumented) static ngAcceptInputType_extended: unknown; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -92,9 +92,9 @@ export type MatIconAnchor = MatIconButton; // @public export class MatIconButton extends MatButtonBase { - constructor(); + constructor(...args: unknown[]); // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -107,11 +107,11 @@ export type MatMiniFabAnchor = MatMiniFabButton; // @public export class MatMiniFabButton extends MatButtonBase { - constructor(); + constructor(...args: unknown[]); // (undocumented) _isFab: boolean; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } diff --git a/goldens/material/button/testing/index.api.md b/goldens/material/button/testing/index.api.md index 3d82c0b2fa40..c57ebcb1c0d9 100644 --- a/goldens/material/button/testing/index.api.md +++ b/goldens/material/button/testing/index.api.md @@ -42,7 +42,6 @@ export class MatButtonHarness extends ContentContainerComponentHarness { static hostSelector: string; isDisabled(): Promise; isFocused(): Promise; - isShowingProgress(): Promise; static with(this: ComponentHarnessConstructor, options?: ButtonHarnessFilters): HarnessPredicate; } diff --git a/goldens/material/card/index.api.md b/goldens/material/card/index.api.md index 5cb831a40816..0a87a3c7039d 100644 --- a/goldens/material/card/index.api.md +++ b/goldens/material/card/index.api.md @@ -13,7 +13,7 @@ export const MAT_CARD_CONFIG: InjectionToken; // @public export class MatCard { - constructor(); + constructor(...args: unknown[]); // (undocumented) appearance: MatCardAppearance; // (undocumented) diff --git a/goldens/material/checkbox/index.api.md b/goldens/material/checkbox/index.api.md index 9e72c1808591..23ed540bffeb 100644 --- a/goldens/material/checkbox/index.api.md +++ b/goldens/material/checkbox/index.api.md @@ -23,7 +23,7 @@ export const MAT_CHECKBOX_DEFAULT_OPTIONS: InjectionToken): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) _onBlur(): void; // (undocumented) diff --git a/goldens/material/chips/index.api.md b/goldens/material/chips/index.api.md index 8177083d6078..627c1889451f 100644 --- a/goldens/material/chips/index.api.md +++ b/goldens/material/chips/index.api.md @@ -13,7 +13,6 @@ import { ControlValueAccessor } from '@angular/forms'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FocusKeyManager } from '@angular/cdk/a11y'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; @@ -32,7 +31,7 @@ import { QueryList } from '@angular/core'; import { Subject } from 'rxjs'; // @public -export const MAT_CHIP: InjectionToken; +export const MAT_CHIP: InjectionToken; // @public export const MAT_CHIP_AVATAR: InjectionToken; @@ -54,7 +53,7 @@ export const MAT_CHIPS_DEFAULT_OPTIONS: InjectionToken; // @public export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck, OnDestroy { - constructor(); + constructor(...args: unknown[]); protected _allEditIcons: QueryList; protected _allLeadingIcons: QueryList; protected _allRemoveIcons: QueryList; @@ -164,6 +163,7 @@ export interface MatChipEditedEvent extends MatChipEvent { // @public export class MatChipEditInput { + constructor(...args: unknown[]); // (undocumented) getNativeElement(): HTMLElement; // (undocumented) @@ -185,7 +185,7 @@ export interface MatChipEvent { // @public export class MatChipGrid extends MatChipSet implements AfterContentInit, AfterViewInit, ControlValueAccessor, DoCheck, MatFormFieldControl, OnDestroy { - constructor(); + constructor(...args: unknown[]); protected _allowFocusEscape(): void; _blur(): void; readonly change: EventEmitter; @@ -229,8 +229,6 @@ export class MatChipGrid extends MatChipSet implements AfterContentInit, AfterVi set placeholder(value: string); // (undocumented) protected _placeholder: string; - // (undocumented) - protected _redirectDestroyedChipFocus(): void; registerInput(inputElement: MatChipTextControl): void; registerOnChange(fn: (value: any) => void): void; registerOnTouched(fn: () => void): void; @@ -266,7 +264,7 @@ export class MatChipGridChange { // @public export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); addOnBlur: boolean; _blur(): void; readonly chipEnd: EventEmitter; @@ -438,7 +436,7 @@ export class MatChipRemove extends MatChipAction { // @public export class MatChipRow extends MatChip implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected basicChipAttrName: string; contentEditInput?: MatChipEditInput; @@ -493,6 +491,7 @@ export class MatChipSelectionChange { // @public export class MatChipSet implements AfterViewInit, OnDestroy { + constructor(...args: unknown[]); protected _allowFocusEscape(): void; // (undocumented) protected _changeDetectorRef: ChangeDetectorRef; @@ -517,7 +516,6 @@ export class MatChipSet implements AfterViewInit, OnDestroy { protected _hasFocusedChip(): boolean; protected _isValidIndex(index: number): boolean; protected _keyManager: FocusKeyManager; - protected _lastDestroyedFocusedChipIndex: number | null; // (undocumented) static ngAcceptInputType_disabled: unknown; // (undocumented) @@ -527,7 +525,6 @@ export class MatChipSet implements AfterViewInit, OnDestroy { // (undocumented) ngOnDestroy(): void; protected _originatesFromChip(event: Event): boolean; - protected _redirectDestroyedChipFocus(): void; get role(): string | null; set role(value: string | null); protected _skipPredicate(action: MatChipContent): boolean; diff --git a/goldens/material/core/index.api.md b/goldens/material/core/index.api.md index a5bd1221c626..7bb4d1cf2464 100644 --- a/goldens/material/core/index.api.md +++ b/goldens/material/core/index.api.md @@ -9,10 +9,8 @@ import { AfterViewChecked } from '@angular/core'; import { ChangeDetectorRef } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FocusableOption } from '@angular/cdk/a11y'; import { FocusOrigin } from '@angular/cdk/a11y'; -import { FormField } from '@angular/forms/signals'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; import * as i2 from '@angular/cdk/bidi'; @@ -97,8 +95,6 @@ export class ErrorStateMatcher { // (undocumented) isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean; // (undocumented) - isSignalErrorState?(field: Field | null): boolean; - // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) static ɵprov: i0.ɵɵInjectableDeclaration; @@ -106,10 +102,10 @@ export class ErrorStateMatcher { // @public export class _ErrorStateTracker { - constructor(_defaultMatcher: ErrorStateMatcher_2 | null, directive: NgControl | FormField | null, _parentFormGroup: FormGroupDirective | null, _parentForm: NgForm | null, _stateChanges: Subject); + constructor(_defaultMatcher: ErrorStateMatcher_2 | null, ngControl: NgControl | null, _parentFormGroup: FormGroupDirective | null, _parentForm: NgForm | null, _stateChanges: Subject); errorState: boolean; - formField: FormField | null; matcher: ErrorStateMatcher_2; + // (undocumented) ngControl: NgControl | null; updateErrorState(): void; } @@ -162,7 +158,7 @@ export const MATERIAL_ANIMATIONS: InjectionToken; export class _MatInternalFormField { labelPosition: 'before' | 'after'; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration<_MatInternalFormField, "[mat-internal-form-field]", never, { "labelPosition": { "alias": "labelPosition"; "required": true; }; }, {}, never, ["*"], true, never>; + static ɵcmp: i0.ɵɵComponentDeclaration<_MatInternalFormField, "div[mat-internal-form-field]", never, { "labelPosition": { "alias": "labelPosition"; "required": true; }; }, {}, never, ["*"], true, never>; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration<_MatInternalFormField, never>; } @@ -197,7 +193,7 @@ export class MatNativeDateModule { // @public export class MatOptgroup { - constructor(); + constructor(...args: unknown[]); disabled: boolean; _inert: boolean; label: string; @@ -212,7 +208,7 @@ export class MatOptgroup { // @public export class MatOption implements FocusableOption, AfterViewChecked, OnDestroy { - constructor(); + constructor(...args: unknown[]); get active(): boolean; // (undocumented) _changeDetectorRef: ChangeDetectorRef; @@ -285,6 +281,7 @@ export class MatOptionSelectionChange { // @public export class MatPseudoCheckbox { + constructor(...args: unknown[]); // (undocumented) _animationsDisabled: boolean; appearance: 'minimal' | 'full'; @@ -311,7 +308,7 @@ export type MatPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; // @public (undocumented) export class MatRipple implements OnInit, OnDestroy, RippleTarget { - constructor(); + constructor(...args: unknown[]); animation: RippleAnimationConfig; centered: boolean; color: string; @@ -369,7 +366,7 @@ export class MatRippleModule { // @public export class NativeDateAdapter extends DateAdapter { - constructor(); + constructor(...args: unknown[]); // (undocumented) addCalendarDays(date: Date, days: number): Date; // (undocumented) @@ -523,8 +520,6 @@ export class ShowOnDirtyErrorStateMatcher implements ErrorStateMatcher { // (undocumented) isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean; // (undocumented) - isSignalErrorState(field: Field | null): boolean; - // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) static ɵprov: i0.ɵɵInjectableDeclaration; diff --git a/goldens/material/datepicker/index.api.md b/goldens/material/datepicker/index.api.md index c2ae996fb88b..f7d1db2fb8ff 100644 --- a/goldens/material/datepicker/index.api.md +++ b/goldens/material/datepicker/index.api.md @@ -16,7 +16,6 @@ import { Directionality } from '@angular/cdk/bidi'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FocusOrigin } from '@angular/cdk/a11y'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; @@ -100,7 +99,7 @@ export const MAT_DATEPICKER_VALUE_ACCESSOR: any; // @public export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDestroy, OnChanges { - constructor(); + constructor(...args: unknown[]); get activeDate(): D; set activeDate(value: D); protected _activeDrag: MatCalendarUserEvent | null; @@ -131,7 +130,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes // (undocumented) ngAfterViewChecked(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; get selected(): DateRange | D | null; @@ -157,7 +156,7 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes // @public export class MatCalendarBody implements OnChanges, OnDestroy, AfterViewChecked { - constructor(); + constructor(...args: unknown[]); activeCell: number; // (undocumented) readonly activeDateChange: EventEmitter>; @@ -201,7 +200,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy, AfterView // (undocumented) ngAfterViewChecked(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; numCols: number; @@ -230,7 +229,7 @@ export class MatCalendarCell { // (undocumented) compareValue: number; // (undocumented) - readonly cssClasses: string | string[] | Record | undefined; + cssClasses: MatCalendarCellCssClasses; // (undocumented) displayValue: string; // (undocumented) @@ -247,11 +246,13 @@ export class MatCalendarCell { export type MatCalendarCellClassFunction = (date: D, view: 'month' | 'year' | 'multi-year') => MatCalendarCellCssClasses; // @public -export type MatCalendarCellCssClasses = string | string[] | Set | Record; +export type MatCalendarCellCssClasses = string | string[] | Set | { + [key: string]: any; +}; // @public export class MatCalendarHeader { - constructor(); + constructor(...args: unknown[]); // (undocumented) calendar: MatCalendar; currentPeriodClicked(): void; @@ -293,6 +294,7 @@ export class MatDatepicker extends MatDatepickerBase, // @public export class MatDatepickerActions implements AfterViewInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) ngAfterViewInit(): void; // (undocumented) @@ -307,6 +309,7 @@ export class MatDatepickerActions implements AfterViewInit, OnDestroy { // @public export class MatDatepickerApply { + constructor(...args: unknown[]); // (undocumented) _applySelection(): void; // (undocumented) @@ -317,6 +320,7 @@ export class MatDatepickerApply { // @public export class MatDatepickerCancel { + constructor(...args: unknown[]); // (undocumented) _datepicker: MatDatepickerBase, unknown, {}>; // (undocumented) @@ -327,7 +331,7 @@ export class MatDatepickerCancel { // @public export class MatDatepickerContent> implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _actionsPortal: TemplatePortal | null; readonly _animationDone: Subject; // (undocumented) @@ -390,7 +394,7 @@ export interface MatDatepickerControl { // @public export class MatDatepickerInput extends MatDatepickerInputBase implements MatDatepickerControl, OnDestroy { - constructor(); + constructor(...args: unknown[]); protected _ariaOwns: i0.WritableSignal; // (undocumented) protected _assignValueToModel(value: D | null): void; @@ -487,7 +491,7 @@ export interface MatDatepickerPanel, S, D = Ex // @public (undocumented) export class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); ariaLabel: string; _button: MatButton; _customIcon: MatDatepickerToggleIcon; @@ -502,7 +506,7 @@ export class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDe // (undocumented) ngAfterContentInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -524,7 +528,7 @@ export class MatDatepickerToggleIcon { // @public (undocumented) export class MatDateRangeInput implements MatFormFieldControl>, MatDatepickerControl, MatDateRangePickerInput, AfterContentInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); _ariaDescribedBy: string | null; _ariaOwns: i0.WritableSignal; comparisonEnd: D | null; @@ -567,7 +571,7 @@ export class MatDateRangeInput implements MatFormFieldControl>, ngAfterContentInit(): void; ngControl: NgControl | null; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; onContainerClick(): void; @@ -655,7 +659,7 @@ export class MatEndDate extends MatDateRangeInputPartBase { // @public export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); get activeDate(): D; set activeDate(value: D); readonly activeDateChange: EventEmitter; @@ -691,10 +695,10 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { // (undocumented) ngAfterContentInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; - _previewChanged(input: MatCalendarUserEvent | null>): void; + _previewChanged({ event, value: cell }: MatCalendarUserEvent | null>): void; _previewEnd: i0.WritableSignal; _previewStart: i0.WritableSignal; _rangeEnd: i0.WritableSignal; @@ -720,7 +724,7 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { // @public export class MatMultiYearView implements AfterContentInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); get activeDate(): D; set activeDate(value: D); readonly activeDateChange: EventEmitter; @@ -807,7 +811,7 @@ export class MatStartDate extends MatDateRangeInputPartBase { // @public export class MatYearView implements AfterContentInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); get activeDate(): D; set activeDate(value: D); readonly activeDateChange: EventEmitter; diff --git a/goldens/material/datepicker/testing/index.api.md b/goldens/material/datepicker/testing/index.api.md index 8a4267106a5f..55390d4f404a 100644 --- a/goldens/material/datepicker/testing/index.api.md +++ b/goldens/material/datepicker/testing/index.api.md @@ -6,7 +6,6 @@ import { BaseHarnessFilters } from '@angular/cdk/testing'; import { ComponentHarness } from '@angular/cdk/testing'; -import { ContentContainerComponentHarness } from '@angular/cdk/testing'; import { HarnessPredicate } from '@angular/cdk/testing'; import { MatFormFieldControlHarnessBase } from '@angular/material/form-field/testing/control'; import { MatFormFieldControlHarnessFilters } from '@angular/material/form-field/testing/control'; @@ -37,10 +36,6 @@ export enum CalendarView { YEAR = 1 } -// @public -export interface DatepickerActionsHarnessFilters extends BaseHarnessFilters { -} - // @public export interface DatepickerInputHarnessFilters extends MatFormFieldControlHarnessFilters { placeholder?: string | RegExp; @@ -97,15 +92,6 @@ export class MatCalendarHarness extends ComponentHarness { static with(options?: CalendarHarnessFilters): HarnessPredicate; } -// @public -export class MatDatepickerActionsHarness extends ContentContainerComponentHarness { - apply(): Promise; - cancel(): Promise; - // (undocumented) - static hostSelector: string; - static with(options?: DatepickerActionsHarnessFilters): HarnessPredicate; -} - // @public export class MatDatepickerInputHarness extends MatDatepickerInputHarnessBase implements DatepickerTrigger { closeCalendar(): Promise; diff --git a/goldens/material/dialog/index.api.md b/goldens/material/dialog/index.api.md index 95b45db02dc8..49517f7de6db 100644 --- a/goldens/material/dialog/index.api.md +++ b/goldens/material/dialog/index.api.md @@ -4,7 +4,6 @@ ```ts -import { Binding } from '@angular/core'; import { CdkDialogContainer } from '@angular/cdk/dialog'; import { ComponentPortal } from '@angular/cdk/portal'; import { ComponentRef } from '@angular/core'; @@ -27,7 +26,6 @@ import { Observable } from 'rxjs'; import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; -import { RestoreFocusValue } from '@angular/cdk/dialog'; import { ScrollStrategy } from '@angular/cdk/overlay'; import { SimpleChanges } from '@angular/core'; import { Subject } from 'rxjs'; @@ -62,7 +60,7 @@ export const MAT_DIALOG_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>; // @public export class MatDialog implements OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly afterAllClosed: Observable; get afterOpened(): Subject>; closeAll(): void; @@ -99,6 +97,7 @@ export class MatDialogActions extends MatDialogLayoutSection { // @public export class MatDialogClose implements OnInit, OnChanges { + constructor(...args: unknown[]); ariaLabel: string; // (undocumented) dialogRef: MatDialogRef; @@ -106,7 +105,7 @@ export class MatDialogClose implements OnInit, OnChanges { // (undocumented) _matDialogClose: any; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnInit(): void; // (undocumented) @@ -126,7 +125,6 @@ export class MatDialogConfig { ariaModal?: boolean; autoFocus?: AutoFocusTarget | string | boolean; backdropClass?: string | string[]; - bindings?: Binding[]; closeOnNavigation?: boolean; closePredicate?: (result: Result | undefined, config: Config, componentInstance: Component | null) => boolean; data?: D | null; @@ -145,7 +143,7 @@ export class MatDialogConfig { minWidth?: number | string; panelClass?: string | string[]; position?: DialogPosition; - restoreFocus?: RestoreFocusValue; + restoreFocus?: boolean; role?: DialogRole; scrollStrategy?: ScrollStrategy; viewContainerRef?: ViewContainerRef; diff --git a/goldens/material/dialog/testing/index.api.md b/goldens/material/dialog/testing/index.api.md index b9220f42762c..64e0f3067cb4 100644 --- a/goldens/material/dialog/testing/index.api.md +++ b/goldens/material/dialog/testing/index.api.md @@ -6,7 +6,6 @@ import * as _angular_cdk_testing from '@angular/cdk/testing'; import { BaseHarnessFilters } from '@angular/cdk/testing'; -import { Binding } from '@angular/core'; import { CdkDialogContainer } from '@angular/cdk/dialog'; import { ComponentHarnessConstructor } from '@angular/cdk/testing'; import { ComponentPortal } from '@angular/cdk/portal'; @@ -23,7 +22,6 @@ import * as i0 from '@angular/core'; import { Injector } from '@angular/core'; import { Observable } from 'rxjs'; import { OnDestroy } from '@angular/core'; -import { RestoreFocusValue } from '@angular/cdk/dialog'; import { ScrollStrategy } from '@angular/cdk/overlay'; import { Subject } from 'rxjs'; import { TemplateRef } from '@angular/core'; @@ -67,7 +65,7 @@ export enum MatDialogSection { // @public export class MatTestDialogOpener implements OnDestroy { - constructor(); + constructor(...args: unknown[]); closedResult: R | undefined; protected static component: ComponentType | undefined; protected static config: MatDialogConfig | undefined; diff --git a/goldens/material/expansion/index.api.md b/goldens/material/expansion/index.api.md index b2b134b9a589..2f7066c0442c 100644 --- a/goldens/material/expansion/index.api.md +++ b/goldens/material/expansion/index.api.md @@ -82,7 +82,7 @@ export class MatExpansionModule { // @public export class MatExpansionPanel extends CdkAccordionItem implements AfterContentInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); accordion: MatAccordionBase; readonly afterCollapse: EventEmitter; readonly afterExpand: EventEmitter; @@ -96,33 +96,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI get hideToggle(): boolean; set hideToggle(value: boolean); readonly _inputChanges: Subject<{ - hideToggle?: i0.SimpleChange | undefined; - togglePosition?: i0.SimpleChange | undefined; - readonly afterExpand?: i0.SimpleChange> | undefined; - readonly afterCollapse?: i0.SimpleChange> | undefined; - readonly _inputChanges?: i0.SimpleChange> | undefined; - accordion?: i0.SimpleChange | undefined; - _lazyContent?: i0.SimpleChange | undefined; - _body?: i0.SimpleChange> | undefined; - _portal?: i0.SimpleChange> | undefined; - _headerId?: i0.SimpleChange | undefined; - _hasSpacing?: i0.SimpleChange<() => boolean> | undefined; - _getExpandedState?: i0.SimpleChange<() => MatExpansionPanelState> | undefined; - toggle?: i0.SimpleChange<() => void> | undefined; - close?: i0.SimpleChange<() => void> | undefined; - open?: i0.SimpleChange<() => void> | undefined; - ngAfterContentInit?: i0.SimpleChange<() => void> | undefined; - ngOnChanges?: i0.SimpleChange<(changes: /*elided*/ any) => void> | undefined; - ngOnDestroy?: i0.SimpleChange<() => void> | undefined; - _containsFocus?: i0.SimpleChange<() => boolean> | undefined; - readonly closed?: i0.SimpleChange> | undefined; - readonly opened?: i0.SimpleChange> | undefined; - readonly destroyed?: i0.SimpleChange> | undefined; - readonly expandedChange?: i0.SimpleChange> | undefined; - readonly id?: i0.SimpleChange | undefined; - expanded?: i0.SimpleChange | undefined; - disabled?: i0.SimpleChange | undefined; - ngOnInit?: i0.SimpleChange<() => void> | undefined; + [propName: string]: i0.SimpleChange; }>; _lazyContent: MatExpansionPanelContent; // (undocumented) @@ -130,7 +104,7 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI // (undocumented) ngAfterContentInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; open(): void; @@ -156,6 +130,7 @@ export class MatExpansionPanelActionRow { // @public export class MatExpansionPanelContent { + constructor(...args: unknown[]); // (undocumented) _expansionPanel: MatExpansionPanelBase | null; // (undocumented) @@ -183,7 +158,7 @@ export class MatExpansionPanelDescription { // @public export class MatExpansionPanelHeader implements AfterViewInit, OnDestroy, FocusableOption { - constructor(); + constructor(...args: unknown[]); collapsedHeight: string; get disabled(): boolean; expandedHeight: string; diff --git a/goldens/material/form-field/index.api.md b/goldens/material/form-field/index.api.md index f22a18d98f33..2533e80b18c2 100644 --- a/goldens/material/form-field/index.api.md +++ b/goldens/material/form-field/index.api.md @@ -48,6 +48,7 @@ export const MAT_SUFFIX: InjectionToken; // @public export class MatError { + constructor(...args: unknown[]); // (undocumented) id: string; // (undocumented) @@ -58,7 +59,7 @@ export class MatError { // @public export class MatFormField implements FloatingLabelParent, AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _animateAndLockLabel(): void; // (undocumented) protected readonly _animationsDisabled: boolean; diff --git a/goldens/material/grid-list/index.api.md b/goldens/material/grid-list/index.api.md index ae19837c0c71..0956f0b0b485 100644 --- a/goldens/material/grid-list/index.api.md +++ b/goldens/material/grid-list/index.api.md @@ -22,6 +22,7 @@ export class MatGridAvatarCssMatStyler { // @public (undocumented) export class MatGridList implements MatGridListBase, OnInit, AfterContentChecked, TileStyleTarget { + constructor(...args: unknown[]); get cols(): number; set cols(value: NumberInput); get gutterSize(): string; @@ -51,6 +52,7 @@ export class MatGridListModule { // @public (undocumented) export class MatGridTile { + constructor(...args: unknown[]); get colspan(): number; set colspan(value: NumberInput); // (undocumented) @@ -86,6 +88,7 @@ export class MatGridTileHeaderCssMatStyler { // @public (undocumented) export class MatGridTileText implements AfterContentInit { + constructor(...args: unknown[]); // (undocumented) _lines: QueryList; // (undocumented) diff --git a/goldens/material/grid-list/testing/index.api.md b/goldens/material/grid-list/testing/index.api.md index 31fdbe73ae51..420f85c9aaad 100644 --- a/goldens/material/grid-list/testing/index.api.md +++ b/goldens/material/grid-list/testing/index.api.md @@ -22,7 +22,7 @@ export interface GridTileHarnessFilters extends BaseHarnessFilters { // @public export class MatGridListHarness extends ComponentHarness { getColumns(): Promise; - getTileAtPosition(input: { + getTileAtPosition({ row, column, }: { row: number; column: number; }): Promise; diff --git a/goldens/material/icon/index.api.md b/goldens/material/icon/index.api.md index 3bd4f8e72541..ac06730c2129 100644 --- a/goldens/material/icon/index.api.md +++ b/goldens/material/icon/index.api.md @@ -47,7 +47,7 @@ export const MAT_ICON_LOCATION: InjectionToken; // @public export class MatIcon implements OnInit, AfterViewChecked, OnDestroy { - constructor(); + constructor(...args: unknown[]); get color(): string | null | undefined; set color(value: string | null | undefined); // (undocumented) diff --git a/goldens/material/input/index.api.md b/goldens/material/input/index.api.md index 8f32fa7d9afd..bf4e3eff4ae4 100644 --- a/goldens/material/input/index.api.md +++ b/goldens/material/input/index.api.md @@ -12,7 +12,6 @@ import { AfterViewInit } from '@angular/core'; import { BooleanInput } from '@angular/cdk/coercion'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; import * as i1 from '@angular/cdk/observers'; @@ -42,6 +41,7 @@ export const MAT_INPUT_VALUE_ACCESSOR: InjectionToken<{ // @public export class MatError { + constructor(...args: unknown[]); // (undocumented) id: string; // (undocumented) @@ -52,7 +52,7 @@ export class MatError { // @public export class MatFormField implements FloatingLabelParent, AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _animateAndLockLabel(): void; // (undocumented) protected readonly _animationsDisabled: boolean; @@ -149,7 +149,7 @@ export class MatHint { // @public (undocumented) export class MatInput implements MatFormFieldControl_2, OnChanges, OnDestroy, AfterViewInit, DoCheck { - constructor(); + constructor(...args: unknown[]); autofilled: boolean; controlType: string; get describedByIds(): string[]; diff --git a/goldens/material/list/index.api.md b/goldens/material/list/index.api.md index 6d7e446fef81..7da12192d27f 100644 --- a/goldens/material/list/index.api.md +++ b/goldens/material/list/index.api.md @@ -103,6 +103,7 @@ export class MatListItemAvatar extends _MatListItemGraphicBase { // @public export class _MatListItemGraphicBase { + constructor(...args: unknown[]); // (undocumented) _isAlignedAtStart(): boolean; // (undocumented) @@ -123,6 +124,7 @@ export class MatListItemIcon extends _MatListItemGraphicBase { // @public export class MatListItemLine { + constructor(...args: unknown[]); // (undocumented) _elementRef: ElementRef; // (undocumented) @@ -141,6 +143,7 @@ export class MatListItemMeta { // @public export class MatListItemTitle { + constructor(...args: unknown[]); // (undocumented) _elementRef: ElementRef; // (undocumented) @@ -161,6 +164,9 @@ export class MatListModule { // @public (undocumented) export class MatListOption extends MatListItemBase implements ListOption, OnInit, OnDestroy { + // @deprecated + get checkboxPosition(): MatListOptionTogglePosition; + set checkboxPosition(value: MatListOptionTogglePosition); get color(): ThemePalette; set color(newValue: ThemePalette); focus(): void; @@ -198,13 +204,15 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit get value(): any; set value(newValue: any); // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } // @public -export type MatListOptionTogglePosition = 'before' | 'after'; +type MatListOptionTogglePosition = 'before' | 'after'; +export { MatListOptionTogglePosition as MatListOptionCheckboxPosition } +export { MatListOptionTogglePosition } // @public export class MatListSubheaderCssMatStyler { @@ -226,7 +234,7 @@ export class MatNavList extends MatListBase { // @public (undocumented) export class MatSelectionList extends MatListBase implements SelectionList, ControlValueAccessor, AfterViewInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); color: ThemePalette; compareWith: (o1: any, o2: any) => boolean; deselectAll(): MatListOption[]; @@ -246,7 +254,7 @@ export class MatSelectionList extends MatListBase implements SelectionList, Cont // (undocumented) ngAfterViewInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; _onTouched: () => void; diff --git a/goldens/material/menu/index.api.md b/goldens/material/menu/index.api.md index 0a6dd25c09c3..2b79df3d1af9 100644 --- a/goldens/material/menu/index.api.md +++ b/goldens/material/menu/index.api.md @@ -75,7 +75,7 @@ export class MatContextMenuTrigger extends MatMenuTriggerBase implements OnDestr // @public (undocumented) export class MatMenu implements AfterContentInit, MatMenuPanel, OnInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) addItem(_item: MatMenuItem): void; _allItems: QueryList; @@ -120,7 +120,6 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI overlapTrigger: boolean; overlayPanelClass: string | string[]; _panelAnimationState: 'void' | 'enter'; - get panelClass(): string; set panelClass(classes: string); // (undocumented) readonly panelId: string; @@ -146,6 +145,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI // @public export class MatMenuContent implements OnDestroy { + constructor(...args: unknown[]); attach(context?: any): void; readonly _attached: Subject; detach(): void; @@ -169,7 +169,7 @@ export interface MatMenuDefaultOptions { // @public export class MatMenuItem implements FocusableOption, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _checkDisabled(event: Event): void; disabled: boolean; disableRipple: boolean; @@ -257,7 +257,7 @@ export interface MatMenuPanel { // @public export class MatMenuTrigger extends MatMenuTriggerBase implements AfterContentInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); closeMenu(): void; // @deprecated (undocumented) get _deprecatedMatMenuTriggerFor(): MatMenuPanel | null; diff --git a/goldens/material/paginator/index.api.md b/goldens/material/paginator/index.api.md index cf6853addb2b..9c2af36ee2de 100644 --- a/goldens/material/paginator/index.api.md +++ b/goldens/material/paginator/index.api.md @@ -21,7 +21,6 @@ import { Directionality } from '@angular/cdk/bidi'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FlexibleOverlayPopoverLocation } from '@angular/cdk/overlay'; import { FocusableOption } from '@angular/cdk/a11y'; import { FocusOrigin } from '@angular/cdk/a11y'; @@ -56,7 +55,7 @@ export const MAT_PAGINATOR_DEFAULT_OPTIONS: InjectionToken; // @public (undocumented) export class MatProgressBar implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly animationEnd: EventEmitter; get bufferValue(): number; set bufferValue(v: number); diff --git a/goldens/material/progress-spinner/index.api.md b/goldens/material/progress-spinner/index.api.md index 9960232877ef..cbe9e2d37f91 100644 --- a/goldens/material/progress-spinner/index.api.md +++ b/goldens/material/progress-spinner/index.api.md @@ -14,7 +14,7 @@ export const MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS: InjectionToken; // (undocumented) _checkSelectedRadioButton(): void; diff --git a/goldens/material/select/index.api.md b/goldens/material/select/index.api.md index 9f92404f9399..fb4c41f264fb 100644 --- a/goldens/material/select/index.api.md +++ b/goldens/material/select/index.api.md @@ -20,7 +20,6 @@ import { ControlValueAccessor } from '@angular/forms'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FlexibleOverlayPopoverLocation } from '@angular/cdk/overlay'; import { FocusableOption } from '@angular/cdk/a11y'; import { FocusOrigin } from '@angular/cdk/a11y'; @@ -55,6 +54,7 @@ export const MAT_SELECT_TRIGGER: InjectionToken; // @public export class MatError { + constructor(...args: unknown[]); // (undocumented) id: string; // (undocumented) @@ -65,7 +65,7 @@ export class MatError { // @public export class MatFormField implements FloatingLabelParent, AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _animateAndLockLabel(): void; // (undocumented) protected readonly _animationsDisabled: boolean; @@ -170,7 +170,7 @@ export class MatLabel { // @public export class MatOptgroup { - constructor(); + constructor(...args: unknown[]); disabled: boolean; _inert: boolean; label: string; @@ -185,7 +185,7 @@ export class MatOptgroup { // @public export class MatOption implements FocusableOption, AfterViewChecked, OnDestroy { - constructor(); + constructor(...args: unknown[]); get active(): boolean; // (undocumented) _changeDetectorRef: ChangeDetectorRef; @@ -239,7 +239,7 @@ export class MatPrefix { // @public (undocumented) export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit, DoCheck, ControlValueAccessor, MatFormFieldControl_2 { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected _animationsDisabled: boolean; ariaLabel: string; @@ -309,7 +309,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit // (undocumented) ngDoCheck(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) diff --git a/goldens/material/sidenav/index.api.md b/goldens/material/sidenav/index.api.md index 8c6b4616a8fb..306745f23dab 100644 --- a/goldens/material/sidenav/index.api.md +++ b/goldens/material/sidenav/index.api.md @@ -27,7 +27,7 @@ export const MAT_DRAWER_DEFAULT_AUTOSIZE: InjectionToken; // @public export class MatDrawer implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly _animationEnd: Subject; readonly _animationStarted: Subject; get autoFocus(): AutoFocusTarget | string | boolean; @@ -68,7 +68,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy { // @public export class MatDrawerContainer implements AfterContentInit, DoCheck, OnDestroy { - constructor(); + constructor(...args: unknown[]); _allDrawers: QueryList; get autosize(): boolean; set autosize(value: BooleanInput); @@ -112,20 +112,17 @@ export class MatDrawerContainer implements AfterContentInit, DoCheck, OnDestroy // (undocumented) _userContent: MatDrawerContent; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } // @public (undocumented) export class MatDrawerContent extends CdkScrollable implements AfterContentInit { + constructor(...args: unknown[]); // (undocumented) _container: MatDrawerContainer; // (undocumented) - _drawerModeChanged(): void; - // (undocumented) - _drawerToggled(drawer: MatDrawer): void; - // (undocumented) ngAfterContentInit(): void; protected _shouldBeHidden(): boolean; // (undocumented) @@ -161,7 +158,7 @@ export class MatSidenavContainer extends MatDrawerContainer { // (undocumented) _content: MatSidenavContent; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } diff --git a/goldens/material/slide-toggle/index.api.md b/goldens/material/slide-toggle/index.api.md index c297901a4cf5..4f42898e17b9 100644 --- a/goldens/material/slide-toggle/index.api.md +++ b/goldens/material/slide-toggle/index.api.md @@ -25,7 +25,7 @@ export const MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS: InjectionToken): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; _noopAnimations: boolean; @@ -91,7 +88,7 @@ export class MatSlideToggle implements OnDestroy, AfterContentInit, OnChanges, C validate(control: AbstractControl): ValidationErrors | null; writeValue(value: any): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } diff --git a/goldens/material/slider/index.api.md b/goldens/material/slider/index.api.md index 283b2ed2dfc8..5b7488870ca5 100644 --- a/goldens/material/slider/index.api.md +++ b/goldens/material/slider/index.api.md @@ -16,13 +16,12 @@ import { NgZone } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { QueryList } from '@angular/core'; -import { Signal } from '@angular/core'; import { Subject } from 'rxjs'; import { WritableSignal } from '@angular/core'; // @public export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { - constructor(); + constructor(...args: unknown[]); // (undocumented) _cachedLeft: number; // (undocumented) @@ -57,7 +56,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { _isCursorOnSliderThumb(event: PointerEvent, rect: DOMRect): boolean; // (undocumented) _isRange: boolean; - _isRtl: i0.Signal; + _isRtl: boolean; _knobRadius: number; get max(): number; set max(v: number); @@ -152,7 +151,7 @@ export class MatSliderModule { // @public (undocumented) export class MatSliderRangeThumb extends MatSliderThumb implements _MatSliderRangeThumb { - constructor(); + constructor(...args: unknown[]); // (undocumented) readonly _cdr: ChangeDetectorRef; // (undocumented) @@ -197,7 +196,7 @@ export class MatSliderRangeThumb extends MatSliderThumb implements _MatSliderRan // @public export class MatSliderThumb implements _MatSliderThumb, OnDestroy, ControlValueAccessor { - constructor(); + constructor(...args: unknown[]); // (undocumented) blur(): void; // (undocumented) @@ -301,6 +300,7 @@ export class MatSliderThumb implements _MatSliderThumb, OnDestroy, ControlValueA // @public export class MatSliderVisualThumb implements _MatSliderVisualThumb, AfterViewInit, OnDestroy { + constructor(...args: unknown[]); // (undocumented) readonly _cdr: ChangeDetectorRef; discrete: boolean; diff --git a/goldens/material/snack-bar/index.api.md b/goldens/material/snack-bar/index.api.md index f10d49a9ce48..47dd8b7bcac1 100644 --- a/goldens/material/snack-bar/index.api.md +++ b/goldens/material/snack-bar/index.api.md @@ -39,6 +39,7 @@ export const MAT_SNACK_BAR_DEFAULT_OPTIONS: InjectionToken { // @public export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected _animationsDisabled: boolean; _animationState: string; @@ -164,6 +165,7 @@ export type MatSnackBarVerticalPosition = 'top' | 'bottom'; // @public (undocumented) export class SimpleSnackBar implements TextOnlySnackBar { + constructor(...args: unknown[]); action(): void; // (undocumented) data: any; diff --git a/goldens/material/sort/index.api.md b/goldens/material/sort/index.api.md index 93bf0f87427d..e0a4397b8c2b 100644 --- a/goldens/material/sort/index.api.md +++ b/goldens/material/sort/index.api.md @@ -15,6 +15,17 @@ import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { Subject } from 'rxjs'; +// @public @deprecated +export type ArrowViewState = SortDirection | 'hint' | 'active'; + +// @public @deprecated +export interface ArrowViewStateTransition { + // (undocumented) + fromState?: ArrowViewState; + // (undocumented) + toState?: ArrowViewState; +} + // @public export const MAT_SORT_DEFAULT_OPTIONS: InjectionToken; @@ -66,7 +77,7 @@ export interface MatSortDefaultOptions { // @public export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewInit { - constructor(); + constructor(...args: unknown[]); // (undocumented) protected _animationsDisabled: boolean; arrowPosition: SortHeaderArrowPosition; @@ -103,7 +114,7 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public @deprecated +// @public export class MatSortHeaderIntl { readonly changes: Subject; // (undocumented) diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md index 2e341d09a37c..c9661064d98e 100644 --- a/goldens/material/stepper/index.api.md +++ b/goldens/material/stepper/index.api.md @@ -16,7 +16,6 @@ import { CdkStepperNext } from '@angular/cdk/stepper'; import { CdkStepperPrevious } from '@angular/cdk/stepper'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; -import { Field } from '@angular/forms/signals'; import { FocusOrigin } from '@angular/cdk/a11y'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; @@ -37,8 +36,6 @@ import { TemplateRef } from '@angular/core'; export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentInit, OnDestroy { color: ThemePalette; isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean; - // (undocumented) - isSignalErrorState(field: Field | null): boolean; _lazyContent: MatStepContent; // (undocumented) ngAfterContentInit(): void; @@ -54,6 +51,7 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI // @public export class MatStepContent { + constructor(...args: unknown[]); // (undocumented) _template: TemplateRef; // (undocumented) @@ -64,7 +62,7 @@ export class MatStepContent { // @public (undocumented) export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); active: boolean; color: ThemePalette; disableRipple: boolean; @@ -111,12 +109,11 @@ export class MatStepLabel extends CdkStepLabel { // @public (undocumented) export class MatStepper extends CdkStepper implements AfterViewInit, AfterContentInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _animatedContainers: QueryList; readonly animationDone: EventEmitter; get animationDuration(): string; set animationDuration(value: string); - ariaLabel: string | null; color: ThemePalette; disableRipple: boolean; // (undocumented) @@ -139,13 +136,14 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten readonly steps: QueryList; _steps: QueryList; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } // @public export class MatStepperIcon { + constructor(...args: unknown[]); name: StepState; // (undocumented) templateRef: TemplateRef; diff --git a/goldens/material/tabs/index.api.md b/goldens/material/tabs/index.api.md index fc0fe2787563..a9ccbc6a6a56 100644 --- a/goldens/material/tabs/index.api.md +++ b/goldens/material/tabs/index.api.md @@ -65,7 +65,7 @@ export interface _MatInkBarPositioner { // @public export abstract class MatPaginatedTabHeader implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); _alignInkBarToSelectedTab(): void; // (undocumented) _animationsDisabled: boolean; @@ -143,7 +143,7 @@ export abstract class MatPaginatedTabHeader implements AfterContentChecked, Afte // @public (undocumented) export class MatTab implements OnInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); ariaLabel: string; ariaLabelledby: string; bodyClass: string | string[]; @@ -158,7 +158,7 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { // (undocumented) static ngAcceptInputType_disabled: unknown; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; // (undocumented) @@ -177,7 +177,7 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { // @public export class MatTabBody implements OnInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly _afterLeavingCenter: EventEmitter; animationDuration: string; readonly _beforeCentering: EventEmitter; @@ -208,6 +208,7 @@ export type MatTabBodyOriginState = 'left' | 'right'; // @public export class MatTabBodyPortal extends CdkPortalOutlet implements OnInit, OnDestroy { + constructor(...args: unknown[]); ngOnDestroy(): void; ngOnInit(): void; // (undocumented) @@ -227,6 +228,7 @@ export class MatTabChangeEvent { // @public export class MatTabContent { + constructor(...args: unknown[]); // (undocumented) template: TemplateRef; // (undocumented) @@ -237,21 +239,19 @@ export class MatTabContent { // @public export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy { - constructor(); + constructor(...args: unknown[]); alignTabs: string | null; _allTabs: QueryList; readonly animationDone: EventEmitter; - get animationDuration(): MatTabGroupAnimationDuration; - set animationDuration(value: MatTabGroupAnimationDuration); + get animationDuration(): string; + set animationDuration(value: string | number); + // (undocumented) + protected _animationsDisabled(): boolean; ariaLabel: string; ariaLabelledby: string; // @deprecated get backgroundColor(): ThemePalette; set backgroundColor(value: ThemePalette); - // (undocumented) - protected _bodyAnimationDuration: string; - // (undocumented) - protected _bodyAnimationsDisabled(): boolean; protected _bodyCentered(isCenter: boolean): void; color: ThemePalette; get contentTabIndex(): number | null; @@ -271,8 +271,6 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten _getTabIndex(index: number): number; _getTabLabelId(tab: MatTab, index: number): string; _handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void; - // (undocumented) - protected _headerAnimationDuration: string; headerPosition: MatTabHeaderPosition; protected _isServer: boolean; // (undocumented) @@ -396,7 +394,7 @@ export class MatTabLabelWrapper extends InkBarItem { // @public export class MatTabLink extends InkBarItem implements AfterViewInit, OnDestroy, RippleTarget, FocusableOption { - constructor(); + constructor(...args: unknown[]); get active(): boolean; set active(value: boolean); disabled: boolean; @@ -445,8 +443,10 @@ export class MatTabLink extends InkBarItem implements AfterViewInit, OnDestroy, // @public export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit, AfterViewInit { - constructor(); - animationDuration: string; + constructor(...args: unknown[]); + // (undocumented) + get animationDuration(): string; + set animationDuration(value: string | number); get backgroundColor(): ThemePalette; set backgroundColor(value: ThemePalette); color: ThemePalette; @@ -470,8 +470,6 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit // (undocumented) _nextPaginator: ElementRef; // (undocumented) - static ngAcceptInputType_animationDuration: string | number; - // (undocumented) static ngAcceptInputType_disableRipple: unknown; // (undocumented) static ngAcceptInputType_fitInkBarToContent: unknown; diff --git a/goldens/material/toolbar/index.api.md b/goldens/material/toolbar/index.api.md index 6ae619e36b78..2e4c99245b42 100644 --- a/goldens/material/toolbar/index.api.md +++ b/goldens/material/toolbar/index.api.md @@ -12,6 +12,7 @@ import { QueryList } from '@angular/core'; // @public (undocumented) export class MatToolbar implements AfterViewInit { + constructor(...args: unknown[]); color?: string | null; // (undocumented) protected _elementRef: ElementRef; diff --git a/goldens/material/tooltip/index.api.md b/goldens/material/tooltip/index.api.md index 8f5d9bf5b799..fc57ed23f9ba 100644 --- a/goldens/material/tooltip/index.api.md +++ b/goldens/material/tooltip/index.api.md @@ -34,7 +34,7 @@ export const MAT_TOOLTIP_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>; // @public export class MatTooltip implements OnDestroy, AfterViewInit { - constructor(); + constructor(...args: unknown[]); protected _addOffset(position: ConnectedPosition): ConnectedPosition; // (undocumented) protected _dir: Directionality; @@ -92,7 +92,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { // @public export interface MatTooltipDefaultOptions { - detectHoverCapability?: boolean | (() => boolean); + detectHoverCapability?: boolean; disableTooltipInteractivity?: boolean; hideDelay: number; position?: TooltipPosition; @@ -122,14 +122,15 @@ export const TOOLTIP_PANEL_CLASS = "mat-mdc-tooltip-panel"; // @public export class TooltipComponent implements OnDestroy { + constructor(...args: unknown[]); afterHidden(): Observable; _cancelPendingAnimations(): void; // (undocumented) protected _elementRef: ElementRef; - _handleAnimationEnd(input: AnimationEvent): void; + _handleAnimationEnd({ animationName }: AnimationEvent): void; _handleBodyInteraction(): void; // (undocumented) - _handleMouseLeave(input: MouseEvent): void; + _handleMouseLeave({ relatedTarget }: MouseEvent): void; hide(delay: number): void; // (undocumented) _isMultiline: boolean; diff --git a/goldens/material/tree/index.api.md b/goldens/material/tree/index.api.md index ebb0566fc935..b5c3ee46e791 100644 --- a/goldens/material/tree/index.api.md +++ b/goldens/material/tree/index.api.md @@ -112,7 +112,7 @@ export class MatTreeNestedDataSource extends DataSource { // @public export class MatTreeNode extends CdkTreeNode implements OnInit, OnDestroy { - constructor(); + constructor(...args: unknown[]); // @deprecated defaultTabIndex: number; // @deprecated diff --git a/goldens/youtube-player/index.api.md b/goldens/youtube-player/index.api.md index 96fb2799ccd3..5dd87e063db4 100644 --- a/goldens/youtube-player/index.api.md +++ b/goldens/youtube-player/index.api.md @@ -7,8 +7,8 @@ /// import { AfterViewInit } from '@angular/core'; -import * as _angular_core from '@angular/core'; import { ElementRef } from '@angular/core'; +import * as i0 from '@angular/core'; import { InjectionToken } from '@angular/core'; import { Observable } from 'rxjs'; import { OnChanges } from '@angular/core'; @@ -23,7 +23,7 @@ export const YOUTUBE_PLAYER_CONFIG: InjectionToken; // @public export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy { - constructor(); + constructor(...args: unknown[]); readonly apiChange: Observable; disableCookies: boolean; disablePlaceholder: boolean; @@ -69,7 +69,7 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy { // (undocumented) ngAfterViewInit(): void; // (undocumented) - ngOnChanges(changes: SimpleChanges): void; + ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; pauseVideo(): void; @@ -96,9 +96,9 @@ export class YouTubePlayer implements AfterViewInit, OnChanges, OnDestroy { set width(width: number | undefined); youtubeContainer: ElementRef; // (undocumented) - static ɵcmp: _angular_core.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) - static ɵfac: _angular_core.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; } // @public @@ -112,11 +112,11 @@ export interface YouTubePlayerConfig { // @public (undocumented) export class YouTubePlayerModule { // (undocumented) - static ɵfac: _angular_core.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration; // (undocumented) - static ɵinj: _angular_core.ɵɵInjectorDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; // (undocumented) - static ɵmod: _angular_core.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // (No @packageDocumentation comment for this package) diff --git a/guides/getting-started.md b/guides/getting-started.md index 2aad292ed800..9b2d5c174fe1 100644 --- a/guides/getting-started.md +++ b/guides/getting-started.md @@ -18,7 +18,7 @@ The `ng add` command will install Angular Material, the [Component Dev Kit (CDK) 1. Choose a prebuilt theme name, or "custom" for a custom theme: - You can choose from [prebuilt material design themes](https://material.angular.dev/guide/theming#prebuilt-themes) or set up an extensible [custom theme](https://material.angular.dev/guide/theming#getting-started). + You can choose from [prebuilt material design themes](https://material.angular.dev/guide/theming#pre-built-themes) or set up an extensible [custom theme](https://material.angular.dev/guide/theming#defining-a-theme). 2. Set up global Angular Material typography styles: @@ -40,13 +40,16 @@ You're done! Angular Material is now configured to be used in your application. Let's display a slide toggle component in your app and verify that everything works. -You need to import the `MatSlideToggle` component by adding it to your component's `imports`: +You need to import the `MatSlideToggleModule` that you want to display by adding the following lines to +your standalone component's imports, or otherwise your component's `NgModule`. ```ts -import {MatSlideToggle} from '@angular/material/slide-toggle'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -@Component({ - imports: [MatSlideToggle], +@Component ({ + imports: [ + MatSlideToggleModule, + ] }) class AppComponent {} ``` diff --git a/guides/material-2.md b/guides/material-2.md index 14ed66610b06..20eda3f8f900 100644 --- a/guides/material-2.md +++ b/guides/material-2.md @@ -1090,24 +1090,9 @@ for how to read values from an M3 theme). ### Pass a new M3 theme in your global theme styles -Replace your M2 theme with an M3 theme. For v19 and later, use the `mat.theme` -mixin as described in the [theming guide][theming]: - -```scss -@use '@angular/material' as mat; - -html { - @include mat.theme(( - color: mat.$violet-palette, - typography: Roboto, - density: 0, - )); -} -``` - -Alternatively, you can create a theme object with `mat.define-theme` and pass it -to individual component theme mixins. This approach is needed for backwards -compatibility helpers such as `color-variants-backwards-compatibility`. +Create a new M3 theme object using `define-theme` and pass it everywhere you were previously passing +your M2 theme. All Angular Material mixins that take an M2 theme are compatible with M3 themes as +well. ### Update usages of APIs that are not supported for Material 3 themes diff --git a/guides/theming-your-components.md b/guides/theming-your-components.md index 957a743124eb..d7532b4db11b 100644 --- a/guides/theming-your-components.md +++ b/guides/theming-your-components.md @@ -942,7 +942,7 @@ configuration. $theme: mat.m2-define-light-theme(( color: ( - primary: mat.m2-define-palette(mat.$m2-indigo-palette, 500), + primary: mat.define-palette(mat.$indigo-palette, 500), ), ... )); diff --git a/guides/theming.md b/guides/theming.md index c9610de48f1b..d0bb0567a59d 100644 --- a/guides/theming.md +++ b/guides/theming.md @@ -216,43 +216,6 @@ pop-up contexts, such as the date picker. The Material Design density guidance explicitly discourages changes to density for such interactions because they don't compete for space in the application's layout. -## Prebuilt Themes - -Angular Material includes eight prebuilt theme CSS files as an alternative to -defining a custom theme with Sass. Four of these themes use the current Material -3 design system, while the other four use the older Material 2 design system. - -If you want your application to follow the modern Material 3 design language, -make sure to use one of the M3 themes. The M2 themes are provided for backwards -compatibility and will be removed in a future version. - -| Theme | Design system | Light or dark? | Palettes (primary, tertiary) | -|--------------------------|---------------|----------------|------------------------------| -| `azure-blue.css` | **M3** | Light | azure, blue | -| `rose-red.css` | **M3** | Light | rose, red | -| `cyan-orange.css` | **M3** | Dark | cyan, orange | -| `magenta-violet.css` | **M3** | Dark | magenta, violet | -| `deeppurple-amber.css` | M2 | Light | deep-purple, amber | -| `indigo-pink.css` | M2 | Light | indigo, pink | -| `pink-bluegrey.css` | M2 | Dark | pink, blue-grey | -| `purple-green.css` | M2 | Dark | purple, green | - -You can find the prebuilt theme files in the `prebuilt-themes` directory of -Angular Material’s npm package (`@angular/material/prebuilt-themes`). To include -a prebuilt theme in your application, add the chosen CSS file to the `styles` -array of your project’s `angular.json` file: - -```json -"styles": [ - "@angular/material/prebuilt-themes/azure-blue.css" -] -``` - -You can [reference the source code for these prebuilt themes][prebuilt] to see -examples of complete theme definitions. - -[prebuilt]: https://github.com/angular/components/blob/main/src/material/core/theming/prebuilt - ## Color Palettes A color palette is a set of similar colors with different hues ranging from diff --git a/integration/linker/link-packages-test.mjs b/integration/linker/link-packages-test.mjs index a698ac0a3c07..79268409e82b 100644 --- a/integration/linker/link-packages-test.mjs +++ b/integration/linker/link-packages-test.mjs @@ -7,7 +7,7 @@ import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel'; import {NodeJSFileSystem, ConsoleLogger, LogLevel} from '@angular/compiler-cli'; import fs from 'fs'; import path from 'path'; -import {transformSync, traverse} from '@babel/core'; +import babel from '@babel/core'; import {sync as globSync} from 'glob'; import chalk from 'chalk'; @@ -78,7 +78,7 @@ function testPackage(pkg) { // Babel throws errors if the transformation fails. We catch these so that we // can print incompatible entry points with their errors at the end. try { - const {ast} = transformSync(fileContent, { + const {ast} = babel.transformSync(fileContent, { ast: true, filename: diskFilePath, filenameRelative: debugFileName, @@ -86,7 +86,7 @@ function testPackage(pkg) { }); // Naively check if there are any Angular declarations left that haven't been linked. - traverse(ast, { + babel.traverse(ast, { Identifier: astPath => { if (astPath.node.name.startsWith('ɵɵngDeclare')) { throw astPath.buildCodeFrameError( diff --git a/integration/module-tests/find-all-modules.mts b/integration/module-tests/find-all-modules.mts index 8df18b9720f6..bc1c8190a4e4 100644 --- a/integration/module-tests/find-all-modules.mts +++ b/integration/module-tests/find-all-modules.mts @@ -45,7 +45,7 @@ export async function findAllEntryPointsAndExportedModules(packagePath: string) function scanExportsForModules(sf: ts.SourceFile): string[] { const moduleExports: string[] = []; const visit = (node: ts.Node) => { - if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) { + if (ts.isExportDeclaration(node) && ts.isNamedExports(node.exportClause)) { moduleExports.push( ...node.exportClause.elements .filter(e => e.name.text.endsWith('Module')) diff --git a/integration/ng-add-standalone/package.json b/integration/ng-add-standalone/package.json index ca2c092b924a..dd28ec41ffdb 100644 --- a/integration/ng-add-standalone/package.json +++ b/integration/ng-add-standalone/package.json @@ -8,7 +8,6 @@ "watch": "ng build --watch --configuration development", "test": "ng test" }, - "packageManager": "pnpm@11.1.2", "private": true, "dependencies": { "@angular/cdk": "next", diff --git a/integration/ng-add-standalone/pnpm-workspace.yaml b/integration/ng-add-standalone/pnpm-workspace.yaml index fc2bc0f8e87c..0c05f1cdad26 100644 --- a/integration/ng-add-standalone/pnpm-workspace.yaml +++ b/integration/ng-add-standalone/pnpm-workspace.yaml @@ -1,12 +1,6 @@ packages: - . -allowBuilds: - '@parcel/watcher': false - esbuild: false - lmdb: false - msgpackr-extract: false - # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). minimumReleaseAge: 1440 diff --git a/integration/ng-add-standalone/tsconfig.json b/integration/ng-add-standalone/tsconfig.json index 4732e60b4e34..2c875b4075d5 100644 --- a/integration/ng-add-standalone/tsconfig.json +++ b/integration/ng-add-standalone/tsconfig.json @@ -10,6 +10,7 @@ "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, + "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "bundler", "importHelpers": false, diff --git a/integration/ng-add/package.json b/integration/ng-add/package.json index 185c6ef6106e..698d010853a5 100644 --- a/integration/ng-add/package.json +++ b/integration/ng-add/package.json @@ -8,7 +8,6 @@ "watch": "ng build --watch --configuration development", "test": "ng test" }, - "packageManager": "pnpm@11.1.2", "private": true, "dependencies": { "@angular/cdk": "next", diff --git a/integration/ng-add/pnpm-workspace.yaml b/integration/ng-add/pnpm-workspace.yaml index fc2bc0f8e87c..0c05f1cdad26 100644 --- a/integration/ng-add/pnpm-workspace.yaml +++ b/integration/ng-add/pnpm-workspace.yaml @@ -1,12 +1,6 @@ packages: - . -allowBuilds: - '@parcel/watcher': false - esbuild: false - lmdb: false - msgpackr-extract: false - # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). minimumReleaseAge: 1440 diff --git a/integration/ng-add/tsconfig.json b/integration/ng-add/tsconfig.json index 4732e60b4e34..2c875b4075d5 100644 --- a/integration/ng-add/tsconfig.json +++ b/integration/ng-add/tsconfig.json @@ -10,6 +10,7 @@ "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, + "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "bundler", "importHelpers": false, diff --git a/integration/vitest/package.json b/integration/vitest/package.json index 82fa3577913d..a4b1bd160a5c 100644 --- a/integration/vitest/package.json +++ b/integration/vitest/package.json @@ -8,28 +8,27 @@ "watch": "ng build --watch --configuration development", "test": "ng test" }, - "packageManager": "pnpm@11.1.2", "private": true, "dependencies": { "@angular/cdk": "next", - "@angular/common": "^22.0.0-next.9", - "@angular/compiler": "^22.0.0-next.9", - "@angular/core": "^22.0.0-next.9", - "@angular/forms": "^22.0.0-next.9", + "@angular/common": "^21.0.0", + "@angular/compiler": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/forms": "^21.0.0", "@angular/material": "next", - "@angular/platform-browser": "^22.0.0-next.9", - "@angular/router": "^22.0.0-next.9", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", "glob": "latest", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "^0.16.0" }, "devDependencies": { - "@angular/build": "^22.0.0-next.6", - "@angular/cli": "^22.0.0-next.6", - "@angular/compiler-cli": "^22.0.0-next.9", + "@angular/build": "^21.0.3", + "@angular/cli": "^21.0.3", + "@angular/compiler-cli": "^21.0.0", "jsdom": "^27.1.0", - "typescript": "~6.0.2", + "typescript": "~5.9.2", "vitest": "^4.0.8" } } diff --git a/integration/vitest/pnpm-lock.yaml b/integration/vitest/pnpm-lock.yaml index 35f9e6515801..93a8467877e6 100644 --- a/integration/vitest/pnpm-lock.yaml +++ b/integration/vitest/pnpm-lock.yaml @@ -10,28 +10,28 @@ importers: dependencies: '@angular/cdk': specifier: next - version: 21.1.0-next.3(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + version: 21.1.0-next.3(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) '@angular/common': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + specifier: ^21.0.0 + version: 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) '@angular/compiler': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9 + specifier: ^21.0.0 + version: 21.0.6 '@angular/core': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) + specifier: ^21.0.0 + version: 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) '@angular/forms': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + specifier: ^21.0.0 + version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) '@angular/material': specifier: next - version: 21.1.0-next.3(c0b01a0ef9b9693a4830e95da096f862) + version: 21.1.0-next.3(b3a355ba9b1961b10b2ce3d3a7c620ef) '@angular/platform-browser': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) + specifier: ^21.0.0 + version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) '@angular/router': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + specifier: ^21.0.0 + version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) glob: specifier: latest version: 13.0.0 @@ -46,127 +46,125 @@ importers: version: 0.16.0 devDependencies: '@angular/build': - specifier: ^22.0.0-next.6 - version: 22.0.0-next.6(@angular/compiler-cli@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(typescript@6.0.3))(@angular/compiler@22.0.0-next.9)(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(chokidar@5.0.0)(istanbul-lib-instrument@6.0.3)(postcss@8.5.6)(tslib@2.8.1)(typescript@6.0.3)(vitest@4.0.16(jsdom@27.3.0)(sass@1.99.0)) + specifier: ^21.0.3 + version: 21.0.3(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(chokidar@4.0.3)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.16(jsdom@27.3.0)(sass@1.93.2)) '@angular/cli': - specifier: ^22.0.0-next.6 - version: 22.0.0-next.6(chokidar@5.0.0) + specifier: ^21.0.3 + version: 21.0.3(chokidar@4.0.3) '@angular/compiler-cli': - specifier: ^22.0.0-next.9 - version: 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(typescript@6.0.3) + specifier: ^21.0.0 + version: 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) jsdom: specifier: ^27.1.0 version: 27.3.0 typescript: - specifier: ~6.0.2 - version: 6.0.3 + specifier: ~5.9.2 + version: 5.9.3 vitest: specifier: ^4.0.8 - version: 4.0.16(jsdom@27.3.0)(sass@1.99.0) + version: 4.0.16(jsdom@27.3.0)(sass@1.93.2) packages: '@acemir/cssom@0.9.29': resolution: {integrity: sha512-G90x0VW+9nW4dFajtjCoT+NM0scAfH9Mb08IcjgFHYbfiL/lU04dTF9JuVOi3/OH+DJCQdcIseSXkdCB9Ky6JA==} - '@algolia/abtesting@1.16.2': - resolution: {integrity: sha512-n9s6bEV6imdtIEd+BGP7WkA4pEZ5YTdgQ05JQhHwWawHg3hyjpNwC0TShGz6zWhv+jfLDGA/6FFNbySFS0P9cw==} + '@algolia/abtesting@1.6.1': + resolution: {integrity: sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-abtesting@5.50.2': - resolution: {integrity: sha512-52iq0vHy1sphgnwoZyx5PmbEt8hsh+m7jD123LmBs6qy4GK7LbYZIeKd+nSnSipN2zvKRZ2zScS6h9PW3J7SXg==} + '@algolia/client-abtesting@5.40.1': + resolution: {integrity: sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.50.2': - resolution: {integrity: sha512-WpPIUg+cSG2aPUG0gS8Ko9DwRgbRPUZxJkolhL2aCsmSlcEEZT65dILrfg5ovcxtx0Kvr+xtBVsTMtsQWRtPDQ==} + '@algolia/client-analytics@5.40.1': + resolution: {integrity: sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.50.2': - resolution: {integrity: sha512-Gj2MgtArGcsr82kIqRlo6/dCAFjrs2gLByEqyRENuT7ugrSMFuqg1vDzeBjRL1t3EJEJCFtT0PLX3gB8A6Hq4Q==} + '@algolia/client-common@5.40.1': + resolution: {integrity: sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.50.2': - resolution: {integrity: sha512-CUqoid5jDpmrc0oK3/xuZXFt6kwT0P9Lw7/nsM14YTr6puvmi+OUKmURpmebQF22S2vCG8L1DAoXXujxQUi/ug==} + '@algolia/client-insights@5.40.1': + resolution: {integrity: sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.50.2': - resolution: {integrity: sha512-AndZWFoc0gbP5901OeQJ73BazgGgSGiBEba4ohdoJuZwHTO2Gio8Q4L1VLmytMBYcviVigB0iICToMvEJxI4ug==} + '@algolia/client-personalization@5.40.1': + resolution: {integrity: sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.50.2': - resolution: {integrity: sha512-NWoL+psEkz5dIzweaByVXuEB45wS8/rk0E0AhMMnaVJdVs7TcACPH2/OURm+N0xRDITkTHqCna823rd6Uqntdg==} + '@algolia/client-query-suggestions@5.40.1': + resolution: {integrity: sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.50.2': - resolution: {integrity: sha512-ypSboUJ3XJoQz5DeDo82hCnrRuwq3q9ZdFhVKAik9TnZh1DvLqoQsrbBjXg7C7zQOtV/Qbge/HmyoV6V5L7MhQ==} + '@algolia/client-search@5.40.1': + resolution: {integrity: sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.50.2': - resolution: {integrity: sha512-VlR2FRXLw2bCB94SQo6zxg/Qi+547aOji6Pb+dKE7h1DMCCY317St+OpjpmgzE+bT2O9ALIc0V4nVIBOd7Gy+Q==} + '@algolia/ingestion@1.40.1': + resolution: {integrity: sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.50.2': - resolution: {integrity: sha512-Cmvfp2+qopzQt8OilU97rhLhosq7ZrB6uieok3EwFUqG/aalPg6DgfCmu0yJMrYe+KMC1qRVt1MTRAUwLknUMQ==} + '@algolia/monitoring@1.40.1': + resolution: {integrity: sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.50.2': - resolution: {integrity: sha512-jrkuyKoOM7dFWQ/6Y4hQAse2SC3L/RldG6GnPjMvAj65h+7Ubb51S0pKk4ofSStF0xm4LCNe0C4T6XX4nOFDiQ==} + '@algolia/recommend@5.40.1': + resolution: {integrity: sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.50.2': - resolution: {integrity: sha512-4107YLJqCudPiBUlwnk6oTSUVwU7ab+qL1SfQGEDYI8DZH5gsf1ekPt9JykXRKYXf2IfouFL5GiCY/PHTFIjYw==} + '@algolia/requester-browser-xhr@5.40.1': + resolution: {integrity: sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.50.2': - resolution: {integrity: sha512-vOrd3MQpLgmf6wXAueTuZ/cA0W4uRwIHHaxNy3h+a6YcNn6bCV/gFdZuv3F13v593zRU2k5R75NmvRWLenvMrw==} + '@algolia/requester-fetch@5.40.1': + resolution: {integrity: sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.50.2': - resolution: {integrity: sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ==} + '@algolia/requester-node-http@5.40.1': + resolution: {integrity: sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==} engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular-devkit/architect@0.2200.0-next.6': - resolution: {integrity: sha512-Eg9Pr6mQUW4/93M2bi8pYshlz7mgRnHgv+pk97TUoXumJATzuldMJE90c+ULcXfMEE+Pkf2dScipKnI/wqp/ag==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - hasBin: true + '@angular-devkit/architect@0.2100.3': + resolution: {integrity: sha512-PcruWF0+IxXOTZd9MN/3y4A5aTfblALzT/+zWym26PtisaBgWQ3tRPQsf/CgT8EdmZl8eUOAWlNBSkbUj/S/lQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/core@22.0.0-next.6': - resolution: {integrity: sha512-ERJI1VgiE5KOV3hPa+k3QdaOLweia9jvVNnWpqmbdUxTq7aNlez6a8E4fITbhycQD/tbQadhDLPrpzBMWmwJFA==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/core@21.0.3': + resolution: {integrity: sha512-X1y3GMYru9+Vt7vz+R8SFAEmDtgf0aZ+1JOpiE7ubHsQOnhA++Pb94HBjQ6CHqlUhQli/XPOBksKNdZkpup8rQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - chokidar: ^5.0.0 + chokidar: ^4.0.0 peerDependenciesMeta: chokidar: optional: true - '@angular-devkit/schematics@22.0.0-next.6': - resolution: {integrity: sha512-XiaqWjBIfbgDCl0q/tkB0iuFSwrqQo4OvnxZWistBI6JtoizSdOdMp4aY/nyAoAfnHL5autg7VOTy0v76Gvn+g==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@21.0.3': + resolution: {integrity: sha512-E/Nja+RIyMzjqLXREOnTRwv7GMrycpAD7kGwDg7l8cWrNQ7phqBZcXAt74Jv9K9aYsOC8tw2Ms9t59aQ6iow8w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular/build@22.0.0-next.6': - resolution: {integrity: sha512-of5UAlCrKskH6nBGGB4al1gEhQhZM2qAXsavug6ucWw7Jf1cIbzjg/i4fdovCcX7HQCrXcL8rXMn173vB6dDrA==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/build@21.0.3': + resolution: {integrity: sha512-3h2s0Igruei1RB/Hmu7nwbKvjJQ2ykNaiicXYuS2muWUBhDg+lm0QsGTGXrQV2BD0M9YdHU4Byh9upiZgMYpjA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler': ^22.0.0-next.0 - '@angular/compiler-cli': ^22.0.0-next.0 - '@angular/core': ^22.0.0-next.0 - '@angular/localize': ^22.0.0-next.0 - '@angular/platform-browser': ^22.0.0-next.0 - '@angular/platform-server': ^22.0.0-next.0 - '@angular/service-worker': ^22.0.0-next.0 - '@angular/ssr': ^22.0.0-next.6 - istanbul-lib-instrument: ^6.0.0 + '@angular/compiler': ^21.0.0 + '@angular/compiler-cli': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/localize': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/service-worker': ^21.0.0 + '@angular/ssr': ^21.0.3 karma: ^6.4.0 less: ^4.2.0 - ng-packagr: ^22.0.0-next.0 + ng-packagr: ^21.0.0 postcss: ^8.4.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 - typescript: '>=6.0 <6.1' + typescript: '>=5.9 <6.0' vitest: ^4.0.8 peerDependenciesMeta: '@angular/core': @@ -181,8 +179,6 @@ packages: optional: true '@angular/ssr': optional: true - istanbul-lib-instrument: - optional: true karma: optional: true less: @@ -204,38 +200,38 @@ packages: '@angular/platform-browser': ^21.0.0-0 || ^21.1.0-0 || ^21.2.0-0 || ^21.3.0-0 || ^22.0.0-0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/cli@22.0.0-next.6': - resolution: {integrity: sha512-xX6xeR0kg4DzwbSSNm2nnbLR+RODMbO+fehchMkhrzI2PotMab6jolepJsfUJusSKzS2b59dlVVO6JR3QVw8XQ==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/cli@21.0.3': + resolution: {integrity: sha512-3lMR3J231JhLgAt37yEULSHFte3zPeta9VYpIIf92JiBsTnWrvKnaK8RXhfdiSQrvhqQ9FMQdl5AG62r1c4dbA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular/common@22.0.0-next.9': - resolution: {integrity: sha512-cjnDWnGjMT/78EEZjQQutQPuUdKO0nfcBlEiG9cL1dliaHpwjLNzOW4TNlNQJr7hz9FSWCWAZAFJugRoZxc8vQ==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/common@21.0.6': + resolution: {integrity: sha512-Yd8PF0dR37FAzqEcBHAyVCiSGMJOezSJe6rV/4BC6AVLfaZ7oZLl8CNVxKsod2UHd6rKxt1hzx05QdVcVvYNeA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 22.0.0-next.9 + '@angular/core': 21.0.6 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@22.0.0-next.9': - resolution: {integrity: sha512-OBFNOsA18miM8U1A4lsCQz5f7THelVH+10NhoI7+YLJZreUUdK1SDlk0pF4MBs2lbYP99k2G35Pm68W1k89f0g==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/compiler-cli@21.0.6': + resolution: {integrity: sha512-UcIUx+fbn0VLlCBCIYxntAzWG3zPRUo0K7wvuK0MC6ZFCWawgewx9SdLLZTqcaWe1g5FRQlQeVQcFgHAO5R2Mw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 22.0.0-next.9 - typescript: '>=6.0 <6.1' + '@angular/compiler': 21.0.6 + typescript: '>=5.9 <6.0' peerDependenciesMeta: typescript: optional: true - '@angular/compiler@22.0.0-next.9': - resolution: {integrity: sha512-O7HroLbQFcl2JLJrEKCu3eob0a2w2nTZ36Bx93pzAsWxNMt6nMcIFv1y0qL5TO5eS2WOUGQXagv4+7U+m+0C4Q==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/compiler@21.0.6': + resolution: {integrity: sha512-rBMzG7WnQMouFfDST+daNSAOVYdtw560645PhlxyVeIeHMlCm0j1jjBgVPGTBNpVgKRdT/sqbi6W6JYkY9mERA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/core@22.0.0-next.9': - resolution: {integrity: sha512-cXE3ZM13xd3d4YfrYCRiDs+EikzDm9qeutwiuZ43f3kEsfgvPkdoYtpSevaG+92752XRwyOan3Lz7TLxKp9i2w==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/core@21.0.6': + resolution: {integrity: sha512-SvWbOkkrsqprYJSBmzQEWkWjfZB/jkRYyFp2ClMJBPqOLxP1a+i3Om2rolcNQjZPz87bs9FszwgRlXUy7sw5cQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/compiler': 22.0.0-next.9 + '@angular/compiler': 21.0.6 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: @@ -244,13 +240,13 @@ packages: zone.js: optional: true - '@angular/forms@22.0.0-next.9': - resolution: {integrity: sha512-5Mf5p2FWKOFYipylvpyXZ7UCFSSWO8oGwkKj4qRpojH8i7T6l8y6uym2dakYZwYvjCZdCMKQss63ldFPObQRJA==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/forms@21.0.6': + resolution: {integrity: sha512-aAkAAKuUrP8U7R4aH/HbmG/CXP90GlML77ECBI5b4qCSb+bvaTEYsaf85mCyTpr9jvGkia2LTe42hPcOuyzdsQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.0.0-next.9 - '@angular/core': 22.0.0-next.9 - '@angular/platform-browser': 22.0.0-next.9 + '@angular/common': 21.0.6 + '@angular/core': 21.0.6 + '@angular/platform-browser': 21.0.6 rxjs: ^6.5.3 || ^7.4.0 '@angular/material@21.1.0-next.3': @@ -263,24 +259,24 @@ packages: '@angular/platform-browser': ^21.0.0-0 || ^21.1.0-0 || ^21.2.0-0 || ^21.3.0-0 || ^22.0.0-0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/platform-browser@22.0.0-next.9': - resolution: {integrity: sha512-YKZiwnWDsDRlyw6MmcpZIO/vb4mMFOPXyZaTNvuSR63rBx/jJ/eo1ocii5k2cW+CMu7ZwuABrWoEBSRughhwyA==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/platform-browser@21.0.6': + resolution: {integrity: sha512-tPk8rlUEBPXIUPRYq6Xu7QhJgKtnVr0dOHHuhyi70biKTupr5VikpZC5X9dy2Q3H3zYbK6MHC6384YMuwfU2kg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 22.0.0-next.9 - '@angular/common': 22.0.0-next.9 - '@angular/core': 22.0.0-next.9 + '@angular/animations': 21.0.6 + '@angular/common': 21.0.6 + '@angular/core': 21.0.6 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@22.0.0-next.9': - resolution: {integrity: sha512-2GnVrAUHZjlXvdEE8Bvwvf8rvXIlKYRaqDE8fwPQNqDEoCWnPo+wEb8V4x0J2PfcwIsAo3nqV77FtjQxt2wvUQ==} - engines: {node: ^22.22.0 || >=24.13.1} + '@angular/router@21.0.6': + resolution: {integrity: sha512-HOfomKq7jRSgxt/uUvpdbB8RNaYuGB/FJQ3BfQCFfGw1O9L3B72b7Hilk6AcjCruul6cfv/kmT4EB6Vqi3dQtA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.0.0-next.9 - '@angular/core': 22.0.0-next.9 - '@angular/platform-browser': 22.0.0-next.9 + '@angular/common': 21.0.6 + '@angular/core': 21.0.6 + '@angular/platform-browser': 21.0.6 rxjs: ^6.5.3 || ^7.4.0 '@asamuzakjp/css-color@4.1.1': @@ -296,34 +292,18 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -332,10 +312,6 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} @@ -344,22 +320,12 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-split-export-declaration@7.24.7': resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} @@ -380,44 +346,23 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -450,28 +395,61 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.27.2': - resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.26.0': + resolution: {integrity: sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.26.0': + resolution: {integrity: sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.2': resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.26.0': + resolution: {integrity: sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-arm@0.27.2': @@ -480,82 +458,124 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} - cpu: [arm] + cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.2': - resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + '@esbuild/android-x64@0.26.0': + resolution: {integrity: sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.26.0': + resolution: {integrity: sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.2': resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.2': - resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + '@esbuild/darwin-x64@0.26.0': + resolution: {integrity: sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.26.0': + resolution: {integrity: sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': - resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + '@esbuild/freebsd-x64@0.26.0': + resolution: {integrity: sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.26.0': + resolution: {integrity: sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.2': resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.26.0': + resolution: {integrity: sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-arm@0.27.2': @@ -564,10 +584,16 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} - cpu: [arm] + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.26.0': + resolution: {integrity: sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.27.2': @@ -576,10 +602,16 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.26.0': + resolution: {integrity: sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.27.2': @@ -588,10 +620,16 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} - cpu: [loong64] + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.26.0': + resolution: {integrity: sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.27.2': @@ -600,10 +638,16 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} - cpu: [mips64el] + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.26.0': + resolution: {integrity: sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.27.2': @@ -612,10 +656,16 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} - cpu: [ppc64] + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.26.0': + resolution: {integrity: sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.27.2': @@ -624,10 +674,16 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} - cpu: [riscv64] + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.26.0': + resolution: {integrity: sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.27.2': @@ -636,273 +692,323 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} - cpu: [s390x] + cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.2': - resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + '@esbuild/linux-x64@0.26.0': + resolution: {integrity: sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.26.0': + resolution: {integrity: sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': - resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + '@esbuild/netbsd-x64@0.26.0': + resolution: {integrity: sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.26.0': + resolution: {integrity: sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': - resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + '@esbuild/openbsd-x64@0.26.0': + resolution: {integrity: sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.2': - resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + '@esbuild/openharmony-arm64@0.26.0': + resolution: {integrity: sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.2': - resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + '@esbuild/sunos-x64@0.26.0': + resolution: {integrity: sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.26.0': + resolution: {integrity: sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.2': resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} - cpu: [arm64] + cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.2': - resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + '@esbuild/win32-ia32@0.26.0': + resolution: {integrity: sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.2': - resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + '@esbuild/win32-x64@0.26.0': + resolution: {integrity: sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@gar/promise-retry@1.0.3': - resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} - engines: {node: ^20.17.0 || >=22.9.0} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@harperfast/extended-iterable@1.0.3': - resolution: {integrity: sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} - '@hono/node-server@1.19.14': - resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} - engines: {node: '>=18.14.1'} + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} peerDependencies: - hono: ^4 - - '@inquirer/ansi@2.0.5': - resolution: {integrity: sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/checkbox@5.1.4': - resolution: {integrity: sha512-w6KF8ZYRvqHhROkOTHXYC3qIV/KYEu5o12oLqQySvch61vrYtRxNSHTONSdJqWiFJPlCUQAHT5OgOIyuTr+MHQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/confirm@5.1.19': + resolution: {integrity: sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/confirm@6.0.12': - resolution: {integrity: sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@11.1.9': - resolution: {integrity: sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/editor@5.1.1': - resolution: {integrity: sha512-6y11LgmNpmn5D2aB5FgnCfBUBK8ZstwLCalyJmORcJZ/WrhOjm16mu6eSqIx8DnErxDqSLr+Jkp+GP8/Nwd5tA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@5.0.13': - resolution: {integrity: sha512-dF2zvrFo9LshkcB23/O1il13kBkBltWIXzut1evfbuBLXMiGIuC45c+ZQ0uukjCDsvI8OWqun4FRYMnzFCQa3g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/external-editor@3.0.0': - resolution: {integrity: sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@2.0.5': - resolution: {integrity: sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} - '@inquirer/input@5.0.12': - resolution: {integrity: sha512-uiMFBl4LqFzJClh80Q3f9hbOFJ6kgkDWI4LjAeBuyO6EanVVMF69AgOvpi1qdqjDSjDN6578B6nky9ceEpI+1Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/number@4.0.12': - resolution: {integrity: sha512-/vrwhEf7Xsuh+YlHF4IjSy3g1cyrQuPaSiHIxCEbLu8qnfvrcvJyCkoktOOF+xV9gSb77/G0n3h04RbMDW2sIg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/password@5.0.12': - resolution: {integrity: sha512-CBh7YHju623lxJRcAOo498ZUwIuMy63bqW/vVq0tQAZVv+lkWlHkP9ealYE1utWSisEShY5VMdzIXRmyEODzcQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/prompts@8.4.2': - resolution: {integrity: sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/prompts@7.9.0': + resolution: {integrity: sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/rawlist@5.2.8': - resolution: {integrity: sha512-Su7FQvp5buZmCymN3PPoYv31ZQQX4ve2j02k7piGgKAWgE+AQRB5YoYVveGXcl3TZ9ldgRMSxj56YfDFmmaqLg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/search@4.1.8': - resolution: {integrity: sha512-fGiHKGD6DyPIYUWxoXnQTeXeyYqSOUrasDMABBmMHUalH/LxkuzY0xVRtimXAt1sUeeyYkVuKQx1bebMuN11Kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/select@5.1.4': - resolution: {integrity: sha512-2kWcGKPMLAXAWRp1AH1SLsQmX+j0QjeljyXMUji9WMZC8nRDO0b7qquIGr6143E7KMLt3VAIGNXzwa/6PXQs4Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/type@4.0.5': - resolution: {integrity: sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -941,50 +1047,50 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@listr2/prompt-adapter-inquirer@4.2.3': - resolution: {integrity: sha512-Co9U3AJ3LW0J8XBHjVoNKA79dMAyFt8EZH3OaKTMcDTj8r+6kG3vSUPq/eGLHT7P0iK3uLaFfhdFYd3033P24g==} - engines: {node: '>=22.13.0'} + '@listr2/prompt-adapter-inquirer@3.0.5': + resolution: {integrity: sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==} + engines: {node: '>=20.0.0'} peerDependencies: - '@inquirer/prompts': '>= 3 < 9' - listr2: 10.2.1 + '@inquirer/prompts': '>= 3 < 8' + listr2: 9.0.5 - '@lmdb/lmdb-darwin-arm64@3.5.4': - resolution: {integrity: sha512-Kk4Kz3iyu1QiLsLZBS9Af1eSKUC8VR2T+/jyE2iAyuGw2VwK08pp5iTbZnXn6sWu0LogO/RFktMxOjiDA2sS3w==} + '@lmdb/lmdb-darwin-arm64@3.4.3': + resolution: {integrity: sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==} cpu: [arm64] os: [darwin] - '@lmdb/lmdb-darwin-x64@3.5.4': - resolution: {integrity: sha512-BEe5Rp3trn26oxoXOVL5HVDoiYmjUDwr8NRPkBOdUdCSBEorKI+7JrZLRKAdxO+G6cGQLgseXk0gR7qIQa7aGw==} + '@lmdb/lmdb-darwin-x64@3.4.3': + resolution: {integrity: sha512-nfGm5pQksBGfaj9uMbjC0YyQreny/Pl7mIDtHtw6g7WQuCgeLullr9FNRsYyKplaEJBPrCVpEjpAznxTBIrXBw==} cpu: [x64] os: [darwin] - '@lmdb/lmdb-linux-arm64@3.5.4': - resolution: {integrity: sha512-cUXEengO8o60v1SWerJTH4/RH4U3+9jC0/4njp2Z9NdmvaGzhKsbRM2wpXuRYrN8tytsoJCg0SvWEWwHAwLbCA==} + '@lmdb/lmdb-linux-arm64@3.4.3': + resolution: {integrity: sha512-uX9eaPqWb740wg5D3TCvU/js23lSRSKT7lJrrQ8IuEG/VLgpPlxO3lHDywU44yFYdGS7pElBn6ioKFKhvALZlw==} cpu: [arm64] os: [linux] - '@lmdb/lmdb-linux-arm@3.5.4': - resolution: {integrity: sha512-SGbFR7816uBcTHc2ZY4S6WyOkl9bICnzqTQd2Mv4V/j24cfds88xx2nC6cm/y8zGQL7Ds31YF/5NGxjgcdM5Hw==} + '@lmdb/lmdb-linux-arm@3.4.3': + resolution: {integrity: sha512-Kjqomp7i0rgSbYSUmv9JnXpS55zYT/YcW3Bdf9oqOTjcH0/8tFAP8MLhu/i9V2pMKIURDZk63Ww49DTK0T3c/Q==} cpu: [arm] os: [linux] - '@lmdb/lmdb-linux-x64@3.5.4': - resolution: {integrity: sha512-Gxq8jpgOWXwd0PUR+c9R2Ik1/uBnGd5GMIIzRRDqABCkvmjtC3KWcyhesV9jSPCz759isl0NlbsstZ2oyvk8lA==} + '@lmdb/lmdb-linux-x64@3.4.3': + resolution: {integrity: sha512-7/8l20D55CfwdMupkc3fNxNJdn4bHsti2X0cp6PwiXlLeSFvAfWs5kCCx+2Cyje4l4GtN//LtKWjTru/9hDJQg==} cpu: [x64] os: [linux] - '@lmdb/lmdb-win32-arm64@3.5.4': - resolution: {integrity: sha512-pKv1DJ1bPZAaHkdFsSz5IDfUG8x9vntgquXF9/Dm2xuupcIe/EkLzylpoBxppFVK5vzbV561Dq26jNY2fIMA7g==} + '@lmdb/lmdb-win32-arm64@3.4.3': + resolution: {integrity: sha512-yWVR0e5Gl35EGJBsAuqPOdjtUYuN8CcTLKrqpQFoM+KsMadViVCulhKNhkcjSGJB88Am5bRPjMro4MBB9FS23Q==} cpu: [arm64] os: [win32] - '@lmdb/lmdb-win32-x64@3.5.4': - resolution: {integrity: sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw==} + '@lmdb/lmdb-win32-x64@3.4.3': + resolution: {integrity: sha512-1JdBkcO0Vrua4LUgr4jAe4FUyluwCeq/pDkBrlaVjX3/BBWP1TzVjCL+TibWNQtPAL1BITXPAhlK5Ru4FBd/hg==} cpu: [x64] os: [win32] - '@modelcontextprotocol/sdk@1.29.0': - resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + '@modelcontextprotocol/sdk@1.24.0': + resolution: {integrity: sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -1064,49 +1170,42 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -1136,6 +1235,9 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@npmcli/agent@4.0.0': resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -1148,9 +1250,9 @@ packages: resolution: {integrity: sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==} engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/installed-package-contents@4.0.0': - resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} - engines: {node: ^20.17.0 || >=22.9.0} + '@npmcli/installed-package-contents@3.0.0': + resolution: {integrity: sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==} + engines: {node: ^18.17.0 || >=20.5.0} hasBin: true '@npmcli/node-gyp@5.0.0': @@ -1161,6 +1263,10 @@ packages: resolution: {integrity: sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==} engines: {node: ^20.17.0 || >=22.9.0} + '@npmcli/promise-spawn@8.0.3': + resolution: {integrity: sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==} + engines: {node: ^18.17.0 || >=20.5.0} + '@npmcli/promise-spawn@9.0.1': resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} engines: {node: ^20.17.0 || >=22.9.0} @@ -1173,6 +1279,9 @@ packages: resolution: {integrity: sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==} engines: {node: ^20.17.0 || >=22.9.0} + '@oxc-project/types@0.96.0': + resolution: {integrity: sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -1202,42 +1311,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1261,33 +1364,104 @@ packages: resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} - '@rollup/rollup-android-arm-eabi@4.53.5': - resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==} - cpu: [arm] + '@rolldown/binding-android-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] os: [android] - '@rollup/rollup-android-arm-eabi@4.60.2': - resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.47': + resolution: {integrity: sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.47': + resolution: {integrity: sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': + resolution: {integrity: sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] - os: [android] + os: [linux] - '@rollup/rollup-android-arm64@4.53.5': - resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': + resolution: {integrity: sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] - os: [android] + os: [linux] - '@rollup/rollup-android-arm64@4.60.2': - resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': + resolution: {integrity: sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': + resolution: {integrity: sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': + resolution: {integrity: sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': + resolution: {integrity: sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + + '@rollup/rollup-android-arm-eabi@4.53.5': + resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==} + cpu: [arm] os: [android] - '@rollup/rollup-darwin-arm64@4.53.5': - resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==} + '@rollup/rollup-android-arm64@4.53.5': + resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==} cpu: [arm64] - os: [darwin] + os: [android] - '@rollup/rollup-darwin-arm64@4.60.2': - resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + '@rollup/rollup-darwin-arm64@4.53.5': + resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==} cpu: [arm64] os: [darwin] @@ -1296,233 +1470,99 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.2': - resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.5': resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.60.2': - resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.5': resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.2': - resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==} cpu: [arm] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': - resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} - cpu: [arm] - os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.5': resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==} cpu: [arm] os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm-musleabihf@4.60.2': - resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} - cpu: [arm] - os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.5': resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==} cpu: [arm64] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-gnu@4.60.2': - resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} - cpu: [arm64] - os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.5': resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==} cpu: [arm64] os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-musl@4.60.2': - resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} - cpu: [arm64] - os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.5': resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==} cpu: [loong64] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-gnu@4.60.2': - resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.2': - resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} - cpu: [loong64] - os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.53.5': resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==} cpu: [ppc64] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-gnu@4.60.2': - resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.2': - resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} - cpu: [ppc64] - os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.53.5': resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==} cpu: [riscv64] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-gnu@4.60.2': - resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} - cpu: [riscv64] - os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.5': resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==} cpu: [riscv64] os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-musl@4.60.2': - resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.5': resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==} cpu: [s390x] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-s390x-gnu@4.60.2': - resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} - cpu: [s390x] - os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.5': resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==} cpu: [x64] os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.2': - resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} - cpu: [x64] - os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.5': resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==} cpu: [x64] os: [linux] - libc: [musl] - - '@rollup/rollup-linux-x64-musl@4.60.2': - resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.2': - resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} - cpu: [x64] - os: [openbsd] '@rollup/rollup-openharmony-arm64@4.53.5': resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-openharmony-arm64@4.60.2': - resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} - cpu: [arm64] - os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.5': resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.60.2': - resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.5': resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.2': - resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.5': resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.2': - resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.5': resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.2': - resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} - cpu: [x64] - os: [win32] - - '@schematics/angular@22.0.0-next.6': - resolution: {integrity: sha512-RxgB/xvQESCD22859QiGwfG/t2EA0U23lzoLuX+TDvPWJGh921E4+olXx7IRI5l/a/6c/FHRThrkQco2juDX4A==} - engines: {node: ^22.22.0 || >=24.13.1, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@schematics/angular@21.0.3': + resolution: {integrity: sha512-XYOI2WOz8B+ydJ8iUHRXrUyjTx+YGdCQ8b2FlXnU46ksIctVU+zt4Zgu6462xeaPwOFYw6+r+TvaBAZ14a82Gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} '@sigstore/bundle@4.0.0': resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} @@ -1559,6 +1599,9 @@ packages: resolution: {integrity: sha512-h5x5ga/hh82COe+GoD4+gKUeV4T3iaYOxqLt41GRKApinPI7DMidhCmNVTjKfhCWFJIGXaFJee07XczdT4jdZQ==} engines: {node: ^20.17.0 || >=22.9.0} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1568,11 +1611,11 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@vitejs/plugin-basic-ssl@2.3.0': - resolution: {integrity: sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==} + '@vitejs/plugin-basic-ssl@2.1.0': + resolution: {integrity: sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} peerDependencies: - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.0.0 || ^7.0.0 '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} @@ -1618,10 +1661,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - agent-base@9.0.0: - resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} - engines: {node: '>= 20'} - ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1633,21 +1672,26 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - - algoliasearch@5.50.2: - resolution: {integrity: sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==} + algoliasearch@5.40.1: + resolution: {integrity: sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==} engines: {node: '>= 14.0.0'} ansi-escapes@7.2.0: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -1659,17 +1703,13 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.9.9: resolution: {integrity: sha512-V8fbOCSeOFvlDj7LLChUcqbZrdKD9RU/VR260piF1790vT0mfLSwGc/Qzxv3IqiTukOpNtItePa0HBpMAj7MDg==} hasBin: true - beasties@0.4.2: - resolution: {integrity: sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw==} - engines: {node: '>=18.0.0'} + beasties@0.3.5: + resolution: {integrity: sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==} + engines: {node: '>=14.0.0'} bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -1684,10 +1724,6 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} - engines: {node: 18 || 20 || >=22} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1734,10 +1770,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1750,8 +1782,8 @@ packages: resolution: {integrity: sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==} engines: {node: '>=18.20'} - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} cli-width@4.1.0: @@ -1762,6 +1794,16 @@ packages: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -1862,6 +1904,9 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -1877,10 +1922,6 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - entities@8.0.0: - resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} - engines: {node: '>=20.19.0'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1907,13 +1948,18 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.26.0: + resolution: {integrity: sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==} engines: {node: '>=18'} hasBin: true - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -1931,8 +1977,8 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} @@ -1949,8 +1995,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@8.4.0: - resolution: {integrity: sha512-gDK8yiqKxrGta+3WtON59arrrw6GLmadA1qoFgYXzdcch8fmKDID2XqO8itsi3f1wufXYPT51387dN6cvVBS3Q==} + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -1962,18 +2008,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} - - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-wrap-ansi@0.2.0: - resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2023,10 +2060,6 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2042,10 +2075,6 @@ packages: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2061,10 +2090,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hono@4.12.15: - resolution: {integrity: sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==} - engines: {node: '>=16.9.0'} - hosted-git-info@9.0.2: resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2091,10 +2116,6 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - https-proxy-agent@9.0.0: - resolution: {integrity: sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==} - engines: {node: '>= 20'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2103,16 +2124,12 @@ packages: resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - ignore-walk@8.0.0: resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} engines: {node: ^20.17.0 || >=22.9.0} - immutable@5.1.5: - resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -2121,6 +2138,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@5.0.0: + resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} + engines: {node: ^18.17.0 || >=20.5.0} + ini@6.0.0: resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2133,10 +2154,18 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} @@ -2205,9 +2234,6 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -2220,12 +2246,12 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} - listr2@10.2.1: - resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} - engines: {node: '>=22.13.0'} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} - lmdb@3.5.4: - resolution: {integrity: sha512-9FKQA6G1MMtqNxfxvSBNXD/axeG2QRjYbNh0/ykRL5xYcRbCm2vXq7B9bhc7nSuKdHzr8/BHIwfPuYYH1UsXXw==} + lmdb@3.4.3: + resolution: {integrity: sha512-GWV1kVi6uhrXWqe+3NXWO73OYe8fto6q8JMo0HOpk1vf8nEyFWgo4CSNJpIFzsOxOrysVUlcO48qRbQfmKd1gA==} hasBin: true log-symbols@7.0.1: @@ -2243,6 +2269,9 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2285,10 +2314,6 @@ packages: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2321,10 +2346,6 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} @@ -2343,9 +2364,9 @@ packages: msgpackr@1.11.8: resolution: {integrity: sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -2379,20 +2400,24 @@ packages: engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - npm-bundled@5.0.0: - resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} - engines: {node: ^20.17.0 || >=22.9.0} + npm-bundled@4.0.0: + resolution: {integrity: sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==} + engines: {node: ^18.17.0 || >=20.5.0} npm-install-checks@8.0.0: resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} engines: {node: ^20.17.0 || >=22.9.0} + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + npm-normalize-package-bin@5.0.0: resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} engines: {node: ^20.17.0 || >=22.9.0} - npm-package-arg@13.0.2: - resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} + npm-package-arg@13.0.1: + resolution: {integrity: sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==} engines: {node: ^20.17.0 || >=22.9.0} npm-packlist@10.0.3: @@ -2432,8 +2457,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - ora@9.3.0: - resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} + ora@9.0.0: + resolution: {integrity: sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==} engines: {node: '>=20'} ordered-binary@1.6.0: @@ -2443,13 +2468,13 @@ packages: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} - pacote@21.5.0: - resolution: {integrity: sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==} + pacote@21.0.3: + resolution: {integrity: sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - parse5-html-rewriting-stream@8.0.1: - resolution: {integrity: sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==} + parse5-html-rewriting-stream@8.0.0: + resolution: {integrity: sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==} parse5-sax-parser@8.0.0: resolution: {integrity: sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==} @@ -2465,14 +2490,13 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.1: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -2490,12 +2514,8 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - piscina@5.1.4: - resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==} + piscina@5.1.3: + resolution: {integrity: sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==} engines: {node: '>=20.x'} pkce-challenge@5.0.1: @@ -2505,12 +2525,6 @@ packages: postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - postcss-safe-parser@7.0.1: - resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} - engines: {node: '>=18.0'} - peerDependencies: - postcss: ^8.4.31 - postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -2551,10 +2565,6 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} - reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -2562,6 +2572,11 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -2573,13 +2588,13 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.53.5: - resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown@1.0.0-beta.47: + resolution: {integrity: sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.60.2: - resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + rollup@4.53.5: + resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2593,8 +2608,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.99.0: - resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} + sass@1.93.2: + resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==} engines: {node: '>=14.0.0'} hasBin: true @@ -2611,11 +2626,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -2666,10 +2676,6 @@ packages: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} - smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -2709,6 +2715,10 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + ssri@12.0.0: + resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} + engines: {node: ^18.17.0 || >=20.5.0} + ssri@13.0.0: resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2723,10 +2733,14 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stdin-discarder@0.3.2: - resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -2735,21 +2749,24 @@ packages: resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} engines: {node: '>=20'} - string-width@8.2.0: - resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} - engines: {node: '>=20'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} strip-ansi@7.1.2: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2762,10 +2779,6 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -2804,11 +2817,15 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + unique-filename@5.0.0: resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2830,16 +2847,16 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - validate-npm-package-name@7.0.2: - resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} - engines: {node: ^20.17.0 || >=22.9.0} + validate-npm-package-name@6.0.2: + resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} + engines: {node: ^18.17.0 || >=20.5.0} vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@7.3.0: - resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2878,8 +2895,8 @@ packages: yaml: optional: true - vite@7.3.2: - resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2956,8 +2973,8 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + watchpack@2.4.4: + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} weak-lru-cache@1.2.2: @@ -2984,6 +3001,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + which@6.0.0: resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2994,9 +3016,9 @@ packages: engines: {node: '>=8'} hasBin: true - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} - engines: {node: '>=20'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} @@ -3046,17 +3068,21 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + zod-to-json-schema@3.25.0: + resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} peerDependencies: - zod: ^3.25.28 || ^4 + zod: ^3.25 || ^4 - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.1.13: + resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} zone.js@0.16.0: resolution: {integrity: sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==} @@ -3065,161 +3091,162 @@ snapshots: '@acemir/cssom@0.9.29': {} - '@algolia/abtesting@1.16.2': + '@algolia/abtesting@1.6.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-abtesting@5.50.2': + '@algolia/client-abtesting@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-analytics@5.50.2': + '@algolia/client-analytics@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-common@5.50.2': {} + '@algolia/client-common@5.40.1': {} - '@algolia/client-insights@5.50.2': + '@algolia/client-insights@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-personalization@5.50.2': + '@algolia/client-personalization@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-query-suggestions@5.50.2': + '@algolia/client-query-suggestions@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-search@5.50.2': + '@algolia/client-search@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/ingestion@1.50.2': + '@algolia/ingestion@1.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/monitoring@1.50.2': + '@algolia/monitoring@1.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/recommend@5.50.2': + '@algolia/recommend@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/requester-browser-xhr@5.50.2': + '@algolia/requester-browser-xhr@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 + '@algolia/client-common': 5.40.1 - '@algolia/requester-fetch@5.50.2': + '@algolia/requester-fetch@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 + '@algolia/client-common': 5.40.1 - '@algolia/requester-node-http@5.50.2': + '@algolia/requester-node-http@5.40.1': dependencies: - '@algolia/client-common': 5.50.2 + '@algolia/client-common': 5.40.1 '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular-devkit/architect@0.2200.0-next.6(chokidar@5.0.0)': + '@angular-devkit/architect@0.2100.3(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 22.0.0-next.6(chokidar@5.0.0) + '@angular-devkit/core': 21.0.3(chokidar@4.0.3) rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-devkit/core@22.0.0-next.6(chokidar@5.0.0)': + '@angular-devkit/core@21.0.3(chokidar@4.0.3)': dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) jsonc-parser: 3.3.1 - picomatch: 4.0.4 + picomatch: 4.0.3 rxjs: 7.8.2 source-map: 0.7.6 optionalDependencies: - chokidar: 5.0.0 + chokidar: 4.0.3 - '@angular-devkit/schematics@22.0.0-next.6(chokidar@5.0.0)': + '@angular-devkit/schematics@21.0.3(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 22.0.0-next.6(chokidar@5.0.0) + '@angular-devkit/core': 21.0.3(chokidar@4.0.3) jsonc-parser: 3.3.1 - magic-string: 0.30.21 - ora: 9.3.0 + magic-string: 0.30.19 + ora: 9.0.0 rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular/build@22.0.0-next.6(@angular/compiler-cli@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(typescript@6.0.3))(@angular/compiler@22.0.0-next.9)(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(chokidar@5.0.0)(istanbul-lib-instrument@6.0.3)(postcss@8.5.6)(tslib@2.8.1)(typescript@6.0.3)(vitest@4.0.16(jsdom@27.3.0)(sass@1.99.0))': + '@angular/build@21.0.3(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(chokidar@4.0.3)(postcss@8.5.6)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.0.16(jsdom@27.3.0)(sass@1.93.2))': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2200.0-next.6(chokidar@5.0.0) - '@angular/compiler': 22.0.0-next.9 - '@angular/compiler-cli': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(typescript@6.0.3) - '@babel/core': 7.29.0 + '@angular-devkit/architect': 0.2100.3(chokidar@4.0.3) + '@angular/compiler': 21.0.6 + '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 6.0.12 - '@vitejs/plugin-basic-ssl': 2.3.0(vite@7.3.2(sass@1.99.0)) - beasties: 0.4.2 + '@inquirer/confirm': 5.1.19 + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.2.2(sass@1.93.2)) + beasties: 0.3.5 browserslist: 4.28.1 - esbuild: 0.28.0 - https-proxy-agent: 9.0.0 + esbuild: 0.26.0 + https-proxy-agent: 7.0.6 + istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 - listr2: 10.2.1 - magic-string: 0.30.21 + listr2: 9.0.5 + magic-string: 0.30.19 mrmime: 2.0.1 - parse5-html-rewriting-stream: 8.0.1 - picomatch: 4.0.4 - piscina: 5.1.4 - rollup: 4.60.2 - sass: 1.99.0 - semver: 7.7.4 + parse5-html-rewriting-stream: 8.0.0 + picomatch: 4.0.3 + piscina: 5.1.3 + rolldown: 1.0.0-beta.47 + sass: 1.93.2 + semver: 7.7.3 source-map-support: 0.5.21 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 tslib: 2.8.1 - typescript: 6.0.3 - vite: 7.3.2(sass@1.99.0) - watchpack: 2.5.1 + typescript: 5.9.3 + undici: 7.16.0 + vite: 7.2.2(sass@1.93.2) + watchpack: 2.4.4 optionalDependencies: - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) - istanbul-lib-instrument: 6.0.3 - lmdb: 3.5.4 + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + lmdb: 3.4.3 postcss: 8.5.6 - vitest: 4.0.16(jsdom@27.3.0)(sass@1.99.0) + vitest: 4.0.16(jsdom@27.3.0)(sass@1.93.2) transitivePeerDependencies: - '@types/node' - chokidar @@ -3233,105 +3260,106 @@ snapshots: - tsx - yaml - '@angular/cdk@21.1.0-next.3(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': + '@angular/cdk@21.1.0-next.3(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) parse5: 8.0.0 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/cli@22.0.0-next.6(chokidar@5.0.0)': + '@angular/cli@21.0.3(chokidar@4.0.3)': dependencies: - '@angular-devkit/architect': 0.2200.0-next.6(chokidar@5.0.0) - '@angular-devkit/core': 22.0.0-next.6(chokidar@5.0.0) - '@angular-devkit/schematics': 22.0.0-next.6(chokidar@5.0.0) - '@inquirer/prompts': 8.4.2 - '@listr2/prompt-adapter-inquirer': 4.2.3(@inquirer/prompts@8.4.2)(listr2@10.2.1) - '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6) - '@schematics/angular': 22.0.0-next.6(chokidar@5.0.0) + '@angular-devkit/architect': 0.2100.3(chokidar@4.0.3) + '@angular-devkit/core': 21.0.3(chokidar@4.0.3) + '@angular-devkit/schematics': 21.0.3(chokidar@4.0.3) + '@inquirer/prompts': 7.9.0 + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.9.0)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.24.0(zod@4.1.13) + '@schematics/angular': 21.0.3(chokidar@4.0.3) '@yarnpkg/lockfile': 1.1.0 - algoliasearch: 5.50.2 - ini: 6.0.0 + algoliasearch: 5.40.1 + ini: 5.0.0 jsonc-parser: 3.3.1 - listr2: 10.2.1 - npm-package-arg: 13.0.2 - pacote: 21.5.0 - parse5-html-rewriting-stream: 8.0.1 - semver: 7.7.4 + listr2: 9.0.5 + npm-package-arg: 13.0.1 + pacote: 21.0.3 + parse5-html-rewriting-stream: 8.0.0 + resolve: 1.22.11 + semver: 7.7.3 yargs: 18.0.0 - zod: 4.3.6 + zod: 4.1.13 transitivePeerDependencies: - '@cfworker/json-schema' - '@types/node' - chokidar - supports-color - '@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2)': + '@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2)': dependencies: - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(typescript@6.0.3)': + '@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3)': dependencies: - '@angular/compiler': 22.0.0-next.9 - '@babel/core': 7.29.0 + '@angular/compiler': 21.0.6 + '@babel/core': 7.28.4 '@jridgewell/sourcemap-codec': 1.5.5 - chokidar: 5.0.0 + chokidar: 4.0.3 convert-source-map: 1.9.0 reflect-metadata: 0.2.2 semver: 7.7.3 tslib: 2.8.1 yargs: 18.0.0 optionalDependencies: - typescript: 6.0.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@angular/compiler@22.0.0-next.9': + '@angular/compiler@21.0.6': dependencies: tslib: 2.8.1 - '@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)': + '@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 22.0.0-next.9 + '@angular/compiler': 21.0.6 zone.js: 0.16.0 - '@angular/forms@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': + '@angular/forms@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/material@21.1.0-next.3(c0b01a0ef9b9693a4830e95da096f862)': + '@angular/material@21.1.0-next.3(b3a355ba9b1961b10b2ce3d3a7c620ef)': dependencies: - '@angular/cdk': 21.1.0-next.3(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) - '@angular/common': 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/forms': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) - '@angular/platform-browser': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/cdk': 21.1.0-next.3(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/forms': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@angular/platform-browser': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))': + '@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/common': 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) tslib: 2.8.1 - '@angular/router@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': + '@angular/router@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 22.0.0-next.9(@angular/common@22.0.0-next.9(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@22.0.0-next.9(@angular/compiler@22.0.0-next.9)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) rxjs: 7.8.2 tslib: 2.8.1 @@ -3358,18 +3386,8 @@ snapshots: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - optional: true - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.5': - optional: true - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.28.5': {} '@babel/core@7.28.4': dependencies: @@ -3390,27 +3408,6 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color - optional: true - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color '@babel/generator@7.28.5': dependencies: @@ -3419,15 +3416,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - optional: true - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: @@ -3440,15 +3428,6 @@ snapshots: browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - optional: true - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 '@babel/helper-globals@7.28.0': {} @@ -3458,14 +3437,6 @@ snapshots: '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - optional: true - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: @@ -3475,16 +3446,6 @@ snapshots: '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color - optional: true - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color '@babel/helper-split-export-declaration@7.24.7': dependencies: @@ -3500,34 +3461,16 @@ snapshots: dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.5 - optional: true - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 - optional: true - - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.5 '@babel/types': 7.28.5 - optional: true - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 '@babel/traverse@7.28.5': dependencies: @@ -3540,30 +3483,12 @@ snapshots: debug: 4.4.3 transitivePeerDependencies: - supports-color - optional: true - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -3578,269 +3503,365 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.21': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@emnapi/core@1.7.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.1': dependencies: - '@csstools/css-tokenizer': 3.0.4 + tslib: 2.8.1 + optional: true - '@csstools/css-syntax-patches-for-csstree@1.0.21': {} + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true - '@csstools/css-tokenizer@3.0.4': {} + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.26.0': + optional: true '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/aix-ppc64@0.28.0': + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.26.0': optional: true '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm64@0.28.0': + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.26.0': optional: true '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-arm@0.28.0': + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.26.0': optional: true '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/android-x64@0.28.0': + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.26.0': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.28.0': + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.26.0': optional: true '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/darwin-x64@0.28.0': + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.26.0': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.28.0': + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.26.0': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.28.0': + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.26.0': optional: true '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm64@0.28.0': + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.26.0': optional: true '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-arm@0.28.0': + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.26.0': optional: true '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-ia32@0.28.0': + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.26.0': optional: true '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-loong64@0.28.0': + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.26.0': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-mips64el@0.28.0': + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.26.0': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-ppc64@0.28.0': + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.26.0': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.28.0': + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.26.0': optional: true '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-s390x@0.28.0': + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.26.0': optional: true '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/linux-x64@0.28.0': + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.26.0': optional: true '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.28.0': + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.26.0': optional: true '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.28.0': + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.26.0': optional: true '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.28.0': + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.26.0': optional: true '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.28.0': + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.26.0': optional: true '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.28.0': + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.26.0': optional: true '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/sunos-x64@0.28.0': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.2': + '@esbuild/win32-arm64@0.26.0': optional: true - '@esbuild/win32-arm64@0.28.0': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.27.2': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.28.0': + '@esbuild/win32-ia32@0.26.0': optional: true - '@esbuild/win32-x64@0.27.2': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.28.0': + '@esbuild/win32-x64@0.25.12': optional: true - '@gar/promise-retry@1.0.3': {} + '@esbuild/win32-x64@0.26.0': + optional: true - '@harperfast/extended-iterable@1.0.3': + '@esbuild/win32-x64@0.27.2': optional: true - '@hono/node-server@1.19.14(hono@4.12.15)': - dependencies: - hono: 4.12.15 + '@inquirer/ansi@1.0.2': {} - '@inquirer/ansi@2.0.5': {} + '@inquirer/checkbox@4.3.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 - '@inquirer/checkbox@5.1.4': + '@inquirer/confirm@5.1.19': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 - '@inquirer/confirm@6.0.12': + '@inquirer/confirm@5.1.21': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 - '@inquirer/core@11.1.9': + '@inquirer/core@10.3.2': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5 + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 cli-width: 4.1.0 - fast-wrap-ansi: 0.2.0 - mute-stream: 3.0.0 + mute-stream: 2.0.0 signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 - '@inquirer/editor@5.1.1': + '@inquirer/editor@4.2.23': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/external-editor': 3.0.0 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/external-editor': 1.0.3 + '@inquirer/type': 3.0.10 - '@inquirer/expand@5.0.13': + '@inquirer/expand@4.0.23': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 - '@inquirer/external-editor@3.0.0': + '@inquirer/external-editor@1.0.3': dependencies: chardet: 2.1.1 - iconv-lite: 0.7.2 + iconv-lite: 0.7.1 - '@inquirer/figures@2.0.5': {} + '@inquirer/figures@1.0.15': {} - '@inquirer/input@5.0.12': + '@inquirer/input@4.3.1': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 - '@inquirer/number@4.0.12': + '@inquirer/number@3.0.23': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 - '@inquirer/password@5.0.12': + '@inquirer/password@4.0.23': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 - '@inquirer/prompts@8.4.2': + '@inquirer/prompts@7.9.0': dependencies: - '@inquirer/checkbox': 5.1.4 - '@inquirer/confirm': 6.0.12 - '@inquirer/editor': 5.1.1 - '@inquirer/expand': 5.0.13 - '@inquirer/input': 5.0.12 - '@inquirer/number': 4.0.12 - '@inquirer/password': 5.0.12 - '@inquirer/rawlist': 5.2.8 - '@inquirer/search': 4.1.8 - '@inquirer/select': 5.1.4 + '@inquirer/checkbox': 4.3.2 + '@inquirer/confirm': 5.1.21 + '@inquirer/editor': 4.2.23 + '@inquirer/expand': 4.0.23 + '@inquirer/input': 4.3.1 + '@inquirer/number': 3.0.23 + '@inquirer/password': 4.0.23 + '@inquirer/rawlist': 4.1.11 + '@inquirer/search': 3.2.2 + '@inquirer/select': 4.4.2 - '@inquirer/rawlist@5.2.8': + '@inquirer/rawlist@4.1.11': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 - '@inquirer/search@4.1.8': + '@inquirer/search@3.2.2': dependencies: - '@inquirer/core': 11.1.9 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5 + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 - '@inquirer/select@5.1.4': + '@inquirer/select@4.4.2': dependencies: - '@inquirer/ansi': 2.0.5 - '@inquirer/core': 11.1.9 - '@inquirer/figures': 2.0.5 - '@inquirer/type': 4.0.5 + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + yoctocolors-cjs: 2.1.3 - '@inquirer/type@4.0.5': {} + '@inquirer/type@3.0.10': {} '@isaacs/balanced-match@4.0.1': {} @@ -3852,8 +3873,7 @@ snapshots: dependencies: minipass: 7.1.2 - '@istanbuljs/schema@0.1.3': - optional: true + '@istanbuljs/schema@0.1.3': {} '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -3874,38 +3894,37 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@listr2/prompt-adapter-inquirer@4.2.3(@inquirer/prompts@8.4.2)(listr2@10.2.1)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.9.0)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 8.4.2 - '@inquirer/type': 4.0.5 - listr2: 10.2.1 + '@inquirer/prompts': 7.9.0 + '@inquirer/type': 3.0.10 + listr2: 9.0.5 transitivePeerDependencies: - '@types/node' - '@lmdb/lmdb-darwin-arm64@3.5.4': + '@lmdb/lmdb-darwin-arm64@3.4.3': optional: true - '@lmdb/lmdb-darwin-x64@3.5.4': + '@lmdb/lmdb-darwin-x64@3.4.3': optional: true - '@lmdb/lmdb-linux-arm64@3.5.4': + '@lmdb/lmdb-linux-arm64@3.4.3': optional: true - '@lmdb/lmdb-linux-arm@3.5.4': + '@lmdb/lmdb-linux-arm@3.4.3': optional: true - '@lmdb/lmdb-linux-x64@3.5.4': + '@lmdb/lmdb-linux-x64@3.4.3': optional: true - '@lmdb/lmdb-win32-arm64@3.5.4': + '@lmdb/lmdb-win32-arm64@3.4.3': optional: true - '@lmdb/lmdb-win32-x64@3.5.4': + '@lmdb/lmdb-win32-x64@3.4.3': optional: true - '@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)': + '@modelcontextprotocol/sdk@1.24.0(zod@4.1.13)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.15) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 @@ -3914,14 +3933,12 @@ snapshots: eventsource: 3.0.7 eventsource-parser: 3.0.6 express: 5.2.1 - express-rate-limit: 8.4.0(express@5.2.1) - hono: 4.12.15 + express-rate-limit: 7.5.1(express@5.2.1) jose: 6.1.3 - json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.2(zod@4.3.6) + zod: 4.1.13 + zod-to-json-schema: 3.25.0(zod@4.1.13) transitivePeerDependencies: - supports-color @@ -4015,6 +4032,13 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true + '@napi-rs/wasm-runtime@1.1.0': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@npmcli/agent@4.0.0': dependencies: agent-base: 7.1.4 @@ -4027,7 +4051,7 @@ snapshots: '@npmcli/fs@5.0.0': dependencies: - semver: 7.7.4 + semver: 7.7.3 '@npmcli/git@7.0.1': dependencies: @@ -4037,26 +4061,30 @@ snapshots: npm-pick-manifest: 11.0.3 proc-log: 6.1.0 promise-retry: 2.0.1 - semver: 7.7.4 + semver: 7.7.3 which: 6.0.0 - '@npmcli/installed-package-contents@4.0.0': + '@npmcli/installed-package-contents@3.0.0': dependencies: - npm-bundled: 5.0.0 - npm-normalize-package-bin: 5.0.0 + npm-bundled: 4.0.0 + npm-normalize-package-bin: 4.0.0 '@npmcli/node-gyp@5.0.0': {} '@npmcli/package-json@7.0.4': dependencies: '@npmcli/git': 7.0.1 - glob: 13.0.6 + glob: 13.0.0 hosted-git-info: 9.0.2 json-parse-even-better-errors: 5.0.0 proc-log: 6.1.0 - semver: 7.7.4 + semver: 7.7.3 validate-npm-package-license: 3.0.4 + '@npmcli/promise-spawn@8.0.3': + dependencies: + which: 5.0.0 + '@npmcli/promise-spawn@9.0.1': dependencies: which: 6.0.0 @@ -4074,6 +4102,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@oxc-project/types@0.96.0': {} + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -4135,151 +4165,122 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.1 optional: true - '@rollup/rollup-android-arm-eabi@4.53.5': + '@rolldown/binding-android-arm64@1.0.0-beta.47': optional: true - '@rollup/rollup-android-arm-eabi@4.60.2': + '@rolldown/binding-darwin-arm64@1.0.0-beta.47': optional: true - '@rollup/rollup-android-arm64@4.53.5': + '@rolldown/binding-darwin-x64@1.0.0-beta.47': optional: true - '@rollup/rollup-android-arm64@4.60.2': + '@rolldown/binding-freebsd-x64@1.0.0-beta.47': optional: true - '@rollup/rollup-darwin-arm64@4.53.5': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': optional: true - '@rollup/rollup-darwin-arm64@4.60.2': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': optional: true - '@rollup/rollup-darwin-x64@4.53.5': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': optional: true - '@rollup/rollup-darwin-x64@4.60.2': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': optional: true - '@rollup/rollup-freebsd-arm64@4.53.5': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': optional: true - '@rollup/rollup-freebsd-arm64@4.60.2': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': optional: true - '@rollup/rollup-freebsd-x64@4.53.5': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 optional: true - '@rollup/rollup-freebsd-x64@4.60.2': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.5': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.5': - optional: true + '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rollup/rollup-linux-arm-musleabihf@4.60.2': + '@rollup/rollup-android-arm-eabi@4.53.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.5': + '@rollup/rollup-android-arm64@4.53.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.2': + '@rollup/rollup-darwin-arm64@4.53.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.5': + '@rollup/rollup-darwin-x64@4.53.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.2': + '@rollup/rollup-freebsd-arm64@4.53.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.5': + '@rollup/rollup-freebsd-x64@4.53.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.2': + '@rollup/rollup-linux-arm-gnueabihf@4.53.5': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.2': + '@rollup/rollup-linux-arm-musleabihf@4.53.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.5': + '@rollup/rollup-linux-arm64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.2': + '@rollup/rollup-linux-arm64-musl@4.53.5': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.2': + '@rollup/rollup-linux-loong64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.5': + '@rollup/rollup-linux-ppc64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.2': + '@rollup/rollup-linux-riscv64-gnu@4.53.5': optional: true '@rollup/rollup-linux-riscv64-musl@4.53.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.2': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.2': - optional: true - '@rollup/rollup-linux-x64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.2': - optional: true - '@rollup/rollup-linux-x64-musl@4.53.5': optional: true - '@rollup/rollup-linux-x64-musl@4.60.2': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.2': - optional: true - '@rollup/rollup-openharmony-arm64@4.53.5': optional: true - '@rollup/rollup-openharmony-arm64@4.60.2': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.2': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.2': - optional: true - '@rollup/rollup-win32-x64-gnu@4.53.5': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.2': - optional: true - '@rollup/rollup-win32-x64-msvc@4.53.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.2': - optional: true - - '@schematics/angular@22.0.0-next.6(chokidar@5.0.0)': + '@schematics/angular@21.0.3(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 22.0.0-next.6(chokidar@5.0.0) - '@angular-devkit/schematics': 22.0.0-next.6(chokidar@5.0.0) + '@angular-devkit/core': 21.0.3(chokidar@4.0.3) + '@angular-devkit/schematics': 21.0.3(chokidar@4.0.3) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -4325,6 +4326,11 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -4334,9 +4340,9 @@ snapshots: '@types/estree@1.0.8': {} - '@vitejs/plugin-basic-ssl@2.3.0(vite@7.3.2(sass@1.99.0))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.2.2(sass@1.93.2))': dependencies: - vite: 7.3.2(sass@1.99.0) + vite: 7.2.2(sass@1.93.2) '@vitest/expect@4.0.16': dependencies: @@ -4347,13 +4353,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.0(sass@1.99.0))': + '@vitest/mocker@4.0.16(vite@7.3.0(sass@1.93.2))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(sass@1.99.0) + vite: 7.3.0(sass@1.93.2) '@vitest/pretty-format@4.0.16': dependencies: @@ -4388,16 +4394,10 @@ snapshots: agent-base@7.1.4: {} - agent-base@9.0.0: {} - ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -4405,47 +4405,44 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - algoliasearch@5.50.2: - dependencies: - '@algolia/abtesting': 1.16.2 - '@algolia/client-abtesting': 5.50.2 - '@algolia/client-analytics': 5.50.2 - '@algolia/client-common': 5.50.2 - '@algolia/client-insights': 5.50.2 - '@algolia/client-personalization': 5.50.2 - '@algolia/client-query-suggestions': 5.50.2 - '@algolia/client-search': 5.50.2 - '@algolia/ingestion': 1.50.2 - '@algolia/monitoring': 1.50.2 - '@algolia/recommend': 5.50.2 - '@algolia/requester-browser-xhr': 5.50.2 - '@algolia/requester-fetch': 5.50.2 - '@algolia/requester-node-http': 5.50.2 + algoliasearch@5.40.1: + dependencies: + '@algolia/abtesting': 1.6.1 + '@algolia/client-abtesting': 5.40.1 + '@algolia/client-analytics': 5.40.1 + '@algolia/client-common': 5.40.1 + '@algolia/client-insights': 5.40.1 + '@algolia/client-personalization': 5.40.1 + '@algolia/client-query-suggestions': 5.40.1 + '@algolia/client-search': 5.40.1 + '@algolia/ingestion': 1.40.1 + '@algolia/monitoring': 1.40.1 + '@algolia/recommend': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 ansi-escapes@7.2.0: dependencies: environment: 1.1.0 + ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@6.2.3: {} assertion-error@2.0.1: {} balanced-match@1.0.2: {} - balanced-match@4.0.4: {} - baseline-browser-mapping@2.9.9: {} - beasties@0.4.2: + beasties@0.3.5: dependencies: css-select: 6.0.0 css-what: 7.0.0 @@ -4455,7 +4452,6 @@ snapshots: picocolors: 1.1.1 postcss: 8.5.6 postcss-media-query-parser: 0.2.3 - postcss-safe-parser: 7.0.1(postcss@8.5.6) bidi-js@1.0.3: dependencies: @@ -4481,10 +4477,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: - dependencies: - balanced-match: 4.0.4 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -4506,7 +4498,7 @@ snapshots: dependencies: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 13.0.6 + glob: 13.0.0 lru-cache: 11.2.4 minipass: 7.1.2 minipass-collect: 2.0.1 @@ -4538,10 +4530,6 @@ snapshots: dependencies: readdirp: 4.1.2 - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - chownr@3.0.0: {} cli-cursor@5.0.0: @@ -4550,10 +4538,10 @@ snapshots: cli-spinners@3.3.0: {} - cli-truncate@5.2.0: + cli-truncate@5.1.1: dependencies: - slice-ansi: 8.0.0 - string-width: 8.2.0 + slice-ansi: 7.1.2 + string-width: 8.1.0 cli-width@4.1.0: {} @@ -4563,6 +4551,14 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 9.0.2 + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -4656,6 +4652,8 @@ snapshots: emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} encoding@0.1.13: @@ -4667,8 +4665,6 @@ snapshots: entities@6.0.1: {} - entities@8.0.0: {} - env-paths@2.2.1: {} environment@1.1.0: {} @@ -4685,6 +4681,64 @@ snapshots: dependencies: es-errors: 1.3.0 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.26.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.26.0 + '@esbuild/android-arm': 0.26.0 + '@esbuild/android-arm64': 0.26.0 + '@esbuild/android-x64': 0.26.0 + '@esbuild/darwin-arm64': 0.26.0 + '@esbuild/darwin-x64': 0.26.0 + '@esbuild/freebsd-arm64': 0.26.0 + '@esbuild/freebsd-x64': 0.26.0 + '@esbuild/linux-arm': 0.26.0 + '@esbuild/linux-arm64': 0.26.0 + '@esbuild/linux-ia32': 0.26.0 + '@esbuild/linux-loong64': 0.26.0 + '@esbuild/linux-mips64el': 0.26.0 + '@esbuild/linux-ppc64': 0.26.0 + '@esbuild/linux-riscv64': 0.26.0 + '@esbuild/linux-s390x': 0.26.0 + '@esbuild/linux-x64': 0.26.0 + '@esbuild/netbsd-arm64': 0.26.0 + '@esbuild/netbsd-x64': 0.26.0 + '@esbuild/openbsd-arm64': 0.26.0 + '@esbuild/openbsd-x64': 0.26.0 + '@esbuild/openharmony-arm64': 0.26.0 + '@esbuild/sunos-x64': 0.26.0 + '@esbuild/win32-arm64': 0.26.0 + '@esbuild/win32-ia32': 0.26.0 + '@esbuild/win32-x64': 0.26.0 + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -4714,35 +4768,6 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 - escalade@3.2.0: {} escape-html@1.0.3: {} @@ -4753,7 +4778,7 @@ snapshots: etag@1.8.1: {} - eventemitter3@5.0.4: {} + eventemitter3@5.0.1: {} eventsource-parser@3.0.6: {} @@ -4765,10 +4790,9 @@ snapshots: exponential-backoff@3.1.3: {} - express-rate-limit@8.4.0(express@5.2.1): + express-rate-limit@7.5.1(express@5.2.1): dependencies: express: 5.2.1 - ip-address: 10.1.0 express@5.2.1: dependencies: @@ -4805,26 +4829,12 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-string-truncated-width@3.0.3: {} - - fast-string-width@3.0.2: - dependencies: - fast-string-truncated-width: 3.0.3 - fast-uri@3.1.0: {} - fast-wrap-ansi@0.2.0: - dependencies: - fast-string-width: 3.0.2 - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4860,8 +4870,6 @@ snapshots: get-east-asian-width@1.4.0: {} - get-east-asian-width@1.5.0: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4888,12 +4896,6 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.1 - glob@13.0.6: - dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -4904,8 +4906,6 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.12.15: {} - hosted-git-info@9.0.2: dependencies: lru-cache: 11.2.4 @@ -4945,13 +4945,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@9.0.0: - dependencies: - agent-base: 9.0.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -4960,29 +4953,33 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - ignore-walk@8.0.0: dependencies: minimatch: 10.1.1 - immutable@5.1.5: {} + immutable@5.1.4: {} imurmurhash@0.1.4: {} inherits@2.0.4: {} + ini@5.0.0: {} + ini@6.0.0: {} ip-address@10.1.0: {} ipaddr.js@1.9.1: {} + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + is-extglob@2.1.1: optional: true + is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.4.0 @@ -5007,8 +5004,7 @@ snapshots: isexe@3.1.1: {} - istanbul-lib-coverage@3.2.2: - optional: true + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: dependencies: @@ -5019,7 +5015,6 @@ snapshots: semver: 7.7.3 transitivePeerDependencies: - supports-color - optional: true jose@6.1.3: {} @@ -5058,38 +5053,36 @@ snapshots: json-schema-traverse@1.0.0: {} - json-schema-typed@8.0.2: {} - json5@2.2.3: {} jsonc-parser@3.3.1: {} jsonparse@1.3.1: {} - listr2@10.2.1: + listr2@9.0.5: dependencies: - cli-truncate: 5.2.0 - eventemitter3: 5.0.4 + cli-truncate: 5.1.1 + colorette: 2.0.20 + eventemitter3: 5.0.1 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 10.0.0 + wrap-ansi: 9.0.2 - lmdb@3.5.4: + lmdb@3.4.3: dependencies: - '@harperfast/extended-iterable': 1.0.3 msgpackr: 1.11.8 node-addon-api: 6.1.0 node-gyp-build-optional-packages: 5.2.2 ordered-binary: 1.6.0 weak-lru-cache: 1.2.2 optionalDependencies: - '@lmdb/lmdb-darwin-arm64': 3.5.4 - '@lmdb/lmdb-darwin-x64': 3.5.4 - '@lmdb/lmdb-linux-arm': 3.5.4 - '@lmdb/lmdb-linux-arm64': 3.5.4 - '@lmdb/lmdb-linux-x64': 3.5.4 - '@lmdb/lmdb-win32-arm64': 3.5.4 - '@lmdb/lmdb-win32-x64': 3.5.4 + '@lmdb/lmdb-darwin-arm64': 3.4.3 + '@lmdb/lmdb-darwin-x64': 3.4.3 + '@lmdb/lmdb-linux-arm': 3.4.3 + '@lmdb/lmdb-linux-arm64': 3.4.3 + '@lmdb/lmdb-linux-x64': 3.4.3 + '@lmdb/lmdb-win32-arm64': 3.4.3 + '@lmdb/lmdb-win32-x64': 3.4.3 optional: true log-symbols@7.0.1: @@ -5111,6 +5104,10 @@ snapshots: dependencies: yallist: 3.1.1 + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5157,10 +5154,6 @@ snapshots: dependencies: '@isaacs/brace-expansion': 5.0.0 - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.5 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -5195,8 +5188,6 @@ snapshots: minipass@7.1.2: {} - minipass@7.1.3: {} - minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -5222,7 +5213,7 @@ snapshots: msgpackr-extract: 3.0.3 optional: true - mute-stream@3.0.0: {} + mute-stream@2.0.0: {} nanoid@3.3.11: {} @@ -5247,7 +5238,7 @@ snapshots: make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.4 + semver: 7.7.3 tar: 7.5.2 tinyglobby: 0.2.15 which: 6.0.0 @@ -5260,22 +5251,24 @@ snapshots: dependencies: abbrev: 4.0.0 - npm-bundled@5.0.0: + npm-bundled@4.0.0: dependencies: - npm-normalize-package-bin: 5.0.0 + npm-normalize-package-bin: 4.0.0 npm-install-checks@8.0.0: dependencies: - semver: 7.7.4 + semver: 7.7.3 + + npm-normalize-package-bin@4.0.0: {} npm-normalize-package-bin@5.0.0: {} - npm-package-arg@13.0.2: + npm-package-arg@13.0.1: dependencies: hosted-git-info: 9.0.2 - proc-log: 6.1.0 - semver: 7.7.4 - validate-npm-package-name: 7.0.2 + proc-log: 5.0.0 + semver: 7.7.3 + validate-npm-package-name: 6.0.2 npm-packlist@10.0.3: dependencies: @@ -5286,8 +5279,8 @@ snapshots: dependencies: npm-install-checks: 8.0.0 npm-normalize-package-bin: 5.0.0 - npm-package-arg: 13.0.2 - semver: 7.7.4 + npm-package-arg: 13.0.1 + semver: 7.7.3 npm-registry-fetch@19.1.1: dependencies: @@ -5297,7 +5290,7 @@ snapshots: minipass: 7.1.2 minipass-fetch: 5.0.0 minizlib: 3.1.0 - npm-package-arg: 13.0.2 + npm-package-arg: 13.0.1 proc-log: 6.1.0 transitivePeerDependencies: - supports-color @@ -5324,7 +5317,7 @@ snapshots: dependencies: mimic-function: 5.0.1 - ora@9.3.0: + ora@9.0.0: dependencies: chalk: 5.6.2 cli-cursor: 5.0.0 @@ -5332,39 +5325,40 @@ snapshots: is-interactive: 2.0.0 is-unicode-supported: 2.1.0 log-symbols: 7.0.1 - stdin-discarder: 0.3.2 + stdin-discarder: 0.2.2 string-width: 8.1.0 + strip-ansi: 7.1.2 ordered-binary@1.6.0: optional: true p-map@7.0.4: {} - pacote@21.5.0: + pacote@21.0.3: dependencies: - '@gar/promise-retry': 1.0.3 '@npmcli/git': 7.0.1 - '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/installed-package-contents': 3.0.0 '@npmcli/package-json': 7.0.4 - '@npmcli/promise-spawn': 9.0.1 + '@npmcli/promise-spawn': 8.0.3 '@npmcli/run-script': 10.0.3 cacache: 20.0.3 fs-minipass: 3.0.3 minipass: 7.1.2 - npm-package-arg: 13.0.2 + npm-package-arg: 13.0.1 npm-packlist: 10.0.3 npm-pick-manifest: 11.0.3 npm-registry-fetch: 19.1.1 - proc-log: 6.1.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 sigstore: 4.0.0 - ssri: 13.0.0 + ssri: 12.0.0 tar: 7.5.2 transitivePeerDependencies: - supports-color - parse5-html-rewriting-stream@8.0.1: + parse5-html-rewriting-stream@8.0.0: dependencies: - entities: 8.0.0 + entities: 6.0.1 parse5: 8.0.0 parse5-sax-parser: 8.0.0 @@ -5380,16 +5374,13 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@2.0.1: dependencies: lru-cache: 11.2.4 minipass: 7.1.2 - path-scurry@2.0.2: - dependencies: - lru-cache: 11.2.4 - minipass: 7.1.3 - path-to-regexp@8.3.0: {} pathe@2.0.3: {} @@ -5401,9 +5392,7 @@ snapshots: picomatch@4.0.3: {} - picomatch@4.0.4: {} - - piscina@5.1.4: + piscina@5.1.3: optionalDependencies: '@napi-rs/nice': 1.1.1 @@ -5411,10 +5400,6 @@ snapshots: postcss-media-query-parser@0.2.3: {} - postcss-safe-parser@7.0.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -5452,12 +5437,16 @@ snapshots: readdirp@4.1.2: {} - readdirp@5.0.0: {} - reflect-metadata@0.2.2: {} require-from-string@2.0.2: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -5467,6 +5456,26 @@ snapshots: rfdc@1.4.1: {} + rolldown@1.0.0-beta.47: + dependencies: + '@oxc-project/types': 0.96.0 + '@rolldown/pluginutils': 1.0.0-beta.47 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.47 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.47 + '@rolldown/binding-darwin-x64': 1.0.0-beta.47 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.47 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.47 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.47 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.47 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.47 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.47 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.47 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.47 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.47 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.47 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.47 + rollup@4.53.5: dependencies: '@types/estree': 1.0.8 @@ -5495,37 +5504,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.53.5 fsevents: 2.3.3 - rollup@4.60.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.2 - '@rollup/rollup-android-arm64': 4.60.2 - '@rollup/rollup-darwin-arm64': 4.60.2 - '@rollup/rollup-darwin-x64': 4.60.2 - '@rollup/rollup-freebsd-arm64': 4.60.2 - '@rollup/rollup-freebsd-x64': 4.60.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 - '@rollup/rollup-linux-arm-musleabihf': 4.60.2 - '@rollup/rollup-linux-arm64-gnu': 4.60.2 - '@rollup/rollup-linux-arm64-musl': 4.60.2 - '@rollup/rollup-linux-loong64-gnu': 4.60.2 - '@rollup/rollup-linux-loong64-musl': 4.60.2 - '@rollup/rollup-linux-ppc64-gnu': 4.60.2 - '@rollup/rollup-linux-ppc64-musl': 4.60.2 - '@rollup/rollup-linux-riscv64-gnu': 4.60.2 - '@rollup/rollup-linux-riscv64-musl': 4.60.2 - '@rollup/rollup-linux-s390x-gnu': 4.60.2 - '@rollup/rollup-linux-x64-gnu': 4.60.2 - '@rollup/rollup-linux-x64-musl': 4.60.2 - '@rollup/rollup-openbsd-x64': 4.60.2 - '@rollup/rollup-openharmony-arm64': 4.60.2 - '@rollup/rollup-win32-arm64-msvc': 4.60.2 - '@rollup/rollup-win32-ia32-msvc': 4.60.2 - '@rollup/rollup-win32-x64-gnu': 4.60.2 - '@rollup/rollup-win32-x64-msvc': 4.60.2 - fsevents: 2.3.3 - router@2.2.0: dependencies: debug: 4.4.3 @@ -5542,10 +5520,10 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.99.0: + sass@1.93.2: dependencies: chokidar: 4.0.3 - immutable: 5.1.5 + immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -5558,8 +5536,6 @@ snapshots: semver@7.7.3: {} - semver@7.7.4: {} - send@1.2.1: dependencies: debug: 4.4.3 @@ -5641,11 +5617,6 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - slice-ansi@8.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - smart-buffer@4.2.0: {} socks-proxy-agent@8.0.5: @@ -5686,6 +5657,10 @@ snapshots: spdx-license-ids@3.0.22: {} + ssri@12.0.0: + dependencies: + minipass: 7.1.2 + ssri@13.0.0: dependencies: minipass: 7.1.2 @@ -5696,7 +5671,13 @@ snapshots: std-env@3.10.0: {} - stdin-discarder@0.3.2: {} + stdin-discarder@0.2.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 string-width@7.2.0: dependencies: @@ -5709,15 +5690,16 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 - string-width@8.2.0: + strip-ansi@6.0.1: dependencies: - get-east-asian-width: 1.5.0 - strip-ansi: 7.1.2 + ansi-regex: 5.0.1 strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 + supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} tar@7.5.2: @@ -5737,11 +5719,6 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - tinyrainbow@3.0.3: {} tldts-core@7.0.19: {} @@ -5781,7 +5758,9 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@6.0.3: {} + typescript@5.9.3: {} + + undici@7.16.0: {} unique-filename@5.0.0: dependencies: @@ -5804,13 +5783,13 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validate-npm-package-name@7.0.2: {} + validate-npm-package-name@6.0.2: {} vary@1.1.2: {} - vite@7.3.0(sass@1.99.0): + vite@7.2.2(sass@1.93.2): dependencies: - esbuild: 0.27.2 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 @@ -5818,24 +5797,24 @@ snapshots: tinyglobby: 0.2.15 optionalDependencies: fsevents: 2.3.3 - sass: 1.99.0 + sass: 1.93.2 - vite@7.3.2(sass@1.99.0): + vite@7.3.0(sass@1.93.2): dependencies: esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.60.2 - tinyglobby: 0.2.16 + rollup: 4.53.5 + tinyglobby: 0.2.15 optionalDependencies: fsevents: 2.3.3 - sass: 1.99.0 + sass: 1.93.2 - vitest@4.0.16(jsdom@27.3.0)(sass@1.99.0): + vitest@4.0.16(jsdom@27.3.0)(sass@1.93.2): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.0(sass@1.99.0)) + '@vitest/mocker': 4.0.16(vite@7.3.0(sass@1.93.2)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -5852,7 +5831,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(sass@1.99.0) + vite: 7.3.0(sass@1.93.2) why-is-node-running: 2.3.0 optionalDependencies: jsdom: 27.3.0 @@ -5873,7 +5852,7 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - watchpack@2.5.1: + watchpack@2.4.4: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 @@ -5898,6 +5877,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.1 + which@6.0.0: dependencies: isexe: 3.1.1 @@ -5907,11 +5890,11 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wrap-ansi@10.0.0: + wrap-ansi@6.2.0: dependencies: - ansi-styles: 6.2.3 - string-width: 8.2.0 - strip-ansi: 7.1.2 + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 wrap-ansi@9.0.2: dependencies: @@ -5946,12 +5929,14 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.2: {} - zod-to-json-schema@3.25.2(zod@4.3.6): + zod-to-json-schema@3.25.0(zod@4.1.13): dependencies: - zod: 4.3.6 + zod: 4.1.13 - zod@4.3.6: {} + zod@4.1.13: {} zone.js@0.16.0: {} diff --git a/integration/vitest/pnpm-workspace.yaml b/integration/vitest/pnpm-workspace.yaml index fc2bc0f8e87c..39e9203b9a8e 100644 --- a/integration/vitest/pnpm-workspace.yaml +++ b/integration/vitest/pnpm-workspace.yaml @@ -1,12 +1,6 @@ packages: - . -allowBuilds: - '@parcel/watcher': false - esbuild: false - lmdb: false - msgpackr-extract: false - # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). minimumReleaseAge: 1440 @@ -14,6 +8,5 @@ minimumReleaseAge: 1440 minimumReleaseAgeExclude: - '@angular-devkit/*' - '@angular/*' - - '@ngtools/webpack' - '@schematics/*' - 'ng-packagr' diff --git a/integration/yarn-pnp-compat/angular.json b/integration/yarn-pnp-compat/angular.json index 165c24157b90..f4a5bf475541 100644 --- a/integration/yarn-pnp-compat/angular.json +++ b/integration/yarn-pnp-compat/angular.json @@ -37,6 +37,11 @@ "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" } ], "fileReplacements": [ diff --git a/integration/yarn-pnp-compat/package.json b/integration/yarn-pnp-compat/package.json index a68bb6b662c7..4b664a5aae7d 100644 --- a/integration/yarn-pnp-compat/package.json +++ b/integration/yarn-pnp-compat/package.json @@ -12,21 +12,21 @@ "private": true, "dependencies": { "@angular/cdk": "next", - "@angular/common": "22.0.0-next.9", - "@angular/compiler": "22.0.0-next.9", - "@angular/core": "22.0.0-next.9", - "@angular/forms": "22.0.0-next.9", + "@angular/common": "20.0.0-rc.2", + "@angular/compiler": "20.0.0-rc.2", + "@angular/core": "20.0.0-rc.2", + "@angular/forms": "20.0.0-rc.2", "@angular/material": "next", - "@angular/platform-browser": "22.0.0-next.9", - "@angular/router": "22.0.0-next.9", + "@angular/platform-browser": "20.0.0-rc.2", + "@angular/router": "20.0.0-rc.2", "rxjs": "^7.5.5", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "22.0.0-next.6", - "@angular/cli": "22.0.0-next.6", - "@angular/compiler-cli": "22.0.0-next.9", + "@angular-devkit/build-angular": "20.0.0-rc.3", + "@angular/cli": "20.0.0-rc.3", + "@angular/compiler-cli": "20.0.0-rc.2", "@types/jasmine": "~5.1.7", "@types/node": "22.15.33", "glob": "^7.2.0", @@ -36,6 +36,6 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "6.0.2" + "typescript": "5.8.3" } } diff --git a/integration/yarn-pnp-compat/tsconfig.json b/integration/yarn-pnp-compat/tsconfig.json index ba30b4eb928c..e526ffd7cc15 100644 --- a/integration/yarn-pnp-compat/tsconfig.json +++ b/integration/yarn-pnp-compat/tsconfig.json @@ -11,6 +11,7 @@ "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, + "downlevelIteration": true, "experimentalDecorators": true, "moduleResolution": "bundler", "importHelpers": true, diff --git a/integration/yarn-pnp-compat/yarn.lock b/integration/yarn-pnp-compat/yarn.lock index 9bc48f6a7264..38ff6fb7ca44 100644 --- a/integration/yarn-pnp-compat/yarn.lock +++ b/integration/yarn-pnp-compat/yarn.lock @@ -5,160 +5,6 @@ __metadata: version: 8 cacheKey: 10 -"@algolia/abtesting@npm:1.16.2": - version: 1.16.2 - resolution: "@algolia/abtesting@npm:1.16.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/7c6658dc035267fb254cd4558324011bf51594888eda7134da5c53e4626816fd0cf7bcdabdad3337e10bfb6e74ed68685f41879e098ffc9b3a0c3752b46dcf06 - languageName: node - linkType: hard - -"@algolia/client-abtesting@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-abtesting@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/cb714dd24e31c3109b8698dc7f9b10b3c164b498c37f55f07e253178f842a65cd547be024fff1abbf635fe03329ffd46405fe7bc9353baf7d37b4dfb6a2ffb81 - languageName: node - linkType: hard - -"@algolia/client-analytics@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-analytics@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/5eadce8c10aff4368e0ecb688bde41daa1112493eb93db79cb6184c08768f6f1b297ee2810344c2b8503e9beaf8675473dc4b6b6f5669afeb43bb719dd54f1ae - languageName: node - linkType: hard - -"@algolia/client-common@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-common@npm:5.50.2" - checksum: 10/b88974980f7af469f5b1388a1a9f73477e256b39661d105b1896be36a823e23609cbe5c643587ab750da0a0a0ebb0ae81b470a7060bd6bda7669ba0d7eebdd9b - languageName: node - linkType: hard - -"@algolia/client-insights@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-insights@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/d981137a7f39c208262d187c08313df640562633321c633d5a46dfb6183893b0fd871e7e32bbb30271d87b90ca6feffe09db20aa10e23c8cce44935d450ca07e - languageName: node - linkType: hard - -"@algolia/client-personalization@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-personalization@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/53aedfb45cd2e31dd582a2ac3f8eba20f7ed80636f539dd42883d205bcd29db3cf749833d421a86f4f1cdbb8cdb9762f4e3d53880e380cd23801cec31571a8df - languageName: node - linkType: hard - -"@algolia/client-query-suggestions@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-query-suggestions@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/9b9ecef950d7a587449d62e44f1678fe39c95eda683d2e687b54fb4748d7b5497fd46ff4cf77fbd37f41f7ca744e4a4821b25ce3c84dc9e351d04459f59d42c4 - languageName: node - linkType: hard - -"@algolia/client-search@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/client-search@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/246d29d0527b468d942595dbd4d5aa3f6342fb1073c7ff83a3f0f344732bd52c3f853a0ec10151ff458d80ce6f12d5fce693eb41311f2301e0cb9c536e89370c - languageName: node - linkType: hard - -"@algolia/ingestion@npm:1.50.2": - version: 1.50.2 - resolution: "@algolia/ingestion@npm:1.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/194e1f42271ea91043b9555da462370b24b0ea0a0ee652158ca78c7d1cba92ef77db6c4d291dc2d92bc91b0427a46a2d3ed3c066cf28fdaf6b06a1dc4fbce362 - languageName: node - linkType: hard - -"@algolia/monitoring@npm:1.50.2": - version: 1.50.2 - resolution: "@algolia/monitoring@npm:1.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/6d698bb815fbf25e429bee6c25f710359863ed8b13d55d357075b087a938c143bf7ffd02caf2b76fa72dfe85c7f289ef9c931b3fababa483aa3c106f53c3aa9f - languageName: node - linkType: hard - -"@algolia/recommend@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/recommend@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/3e0be89ac787f59a43892ff32de8ce915c63c82f397c6b6eef3db2fa23372b07aed9ce8ad273aaa59543bf4563f4b7878779593cc212595c4c7f64fa78233e55 - languageName: node - linkType: hard - -"@algolia/requester-browser-xhr@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/requester-browser-xhr@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - checksum: 10/5975d2c5e7124d2c053bfc949a19f1604d207c03b17e0ebe4a17189125094c3fcf5aa0aa2b89835f03eb7b3f56c9e8463e03b6f6815f1128547cd16a49f5f932 - languageName: node - linkType: hard - -"@algolia/requester-fetch@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/requester-fetch@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - checksum: 10/7012cb51c1d513d23563c6ad34957ceaa7e3c88933b380d4d7641f67c8ce7747569bf0f00fb94418d7ecd68f7f034ca4a0455f655c8a000a3607958cea902316 - languageName: node - linkType: hard - -"@algolia/requester-node-http@npm:5.50.2": - version: 5.50.2 - resolution: "@algolia/requester-node-http@npm:5.50.2" - dependencies: - "@algolia/client-common": "npm:5.50.2" - checksum: 10/2a5fde1d931e70168f1a77f03f4e88cd05ec9308d41ccec7d19be426ac67e0d7e60ac82dd9b1d833d6b91c5740f1ddd020fe5800d114128bbf952674c7859629 - languageName: node - linkType: hard - "@ampproject/remapping@npm:2.3.0, @ampproject/remapping@npm:^2.2.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" @@ -169,89 +15,93 @@ __metadata: languageName: node linkType: hard -"@angular-devkit/architect@npm:0.2200.0-next.6": - version: 0.2200.0-next.6 - resolution: "@angular-devkit/architect@npm:0.2200.0-next.6" +"@angular-devkit/architect@npm:0.2000.0-rc.3": + version: 0.2000.0-rc.3 + resolution: "@angular-devkit/architect@npm:0.2000.0-rc.3" dependencies: - "@angular-devkit/core": "npm:22.0.0-next.6" + "@angular-devkit/core": "npm:20.0.0-rc.3" rxjs: "npm:7.8.2" - bin: - architect: bin/cli.js - checksum: 10/255a4037d2f5a73b009cbe964996d066f968036d52a0e187b7f65c1cecce0625aa1f5e587d819f76e456878dbab891a0c8410c223dc449eff5bf8fb7f4bfe08e + checksum: 10/a45203cbb414bef0d6eb93266c3510f504f49f339533854c04f5742b67c0890760acc57eec2380714a527591b70fe7ec3cccf2ab966ce452b95a4f954c816c99 languageName: node linkType: hard -"@angular-devkit/build-angular@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@angular-devkit/build-angular@npm:22.0.0-next.6" +"@angular-devkit/build-angular@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@angular-devkit/build-angular@npm:20.0.0-rc.3" dependencies: "@ampproject/remapping": "npm:2.3.0" - "@angular-devkit/architect": "npm:0.2200.0-next.6" - "@angular-devkit/build-webpack": "npm:0.2200.0-next.6" - "@angular-devkit/core": "npm:22.0.0-next.6" - "@angular/build": "npm:22.0.0-next.6" - "@babel/core": "npm:7.29.0" - "@babel/generator": "npm:7.29.1" - "@babel/helper-annotate-as-pure": "npm:7.27.3" + "@angular-devkit/architect": "npm:0.2000.0-rc.3" + "@angular-devkit/build-webpack": "npm:0.2000.0-rc.3" + "@angular-devkit/core": "npm:20.0.0-rc.3" + "@angular/build": "npm:20.0.0-rc.3" + "@babel/core": "npm:7.27.1" + "@babel/generator": "npm:7.27.1" + "@babel/helper-annotate-as-pure": "npm:7.27.1" "@babel/helper-split-export-declaration": "npm:7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:7.29.0" - "@babel/plugin-transform-async-to-generator": "npm:7.28.6" - "@babel/plugin-transform-runtime": "npm:7.29.0" - "@babel/preset-env": "npm:7.29.2" - "@babel/runtime": "npm:7.29.2" - "@discoveryjs/json-ext": "npm:1.0.0" - "@ngtools/webpack": "npm:22.0.0-next.6" + "@babel/plugin-transform-async-generator-functions": "npm:7.27.1" + "@babel/plugin-transform-async-to-generator": "npm:7.27.1" + "@babel/plugin-transform-runtime": "npm:7.27.1" + "@babel/preset-env": "npm:7.27.2" + "@babel/runtime": "npm:7.27.1" + "@discoveryjs/json-ext": "npm:0.6.3" + "@ngtools/webpack": "npm:20.0.0-rc.3" + "@vitejs/plugin-basic-ssl": "npm:2.0.0" ansi-colors: "npm:4.1.3" - autoprefixer: "npm:10.5.0" - babel-loader: "npm:10.1.1" - browserslist: "npm:^4.26.0" - copy-webpack-plugin: "npm:14.0.0" - css-loader: "npm:7.1.4" - esbuild: "npm:0.28.0" - esbuild-wasm: "npm:0.28.0" + autoprefixer: "npm:10.4.21" + babel-loader: "npm:10.0.0" + browserslist: "npm:^4.21.5" + copy-webpack-plugin: "npm:13.0.0" + css-loader: "npm:7.1.2" + esbuild: "npm:0.25.4" + esbuild-wasm: "npm:0.25.4" + fast-glob: "npm:3.3.3" http-proxy-middleware: "npm:3.0.5" istanbul-lib-instrument: "npm:6.0.3" jsonc-parser: "npm:3.3.1" karma-source-map-support: "npm:1.4.0" - less: "npm:4.6.4" - less-loader: "npm:12.3.2" + less: "npm:4.3.0" + less-loader: "npm:12.3.0" license-webpack-plugin: "npm:4.0.2" loader-utils: "npm:3.3.1" - mini-css-extract-plugin: "npm:2.10.2" - open: "npm:11.0.0" - ora: "npm:9.3.0" - picomatch: "npm:4.0.4" - piscina: "npm:5.1.4" - postcss: "npm:8.5.10" - postcss-loader: "npm:8.2.1" + mini-css-extract-plugin: "npm:2.9.2" + open: "npm:10.1.2" + ora: "npm:5.4.1" + picomatch: "npm:4.0.2" + piscina: "npm:5.0.0" + postcss: "npm:8.5.3" + postcss-loader: "npm:8.1.1" resolve-url-loader: "npm:5.0.0" rxjs: "npm:7.8.2" - sass: "npm:1.99.0" - sass-loader: "npm:16.0.7" - semver: "npm:7.7.4" + sass: "npm:1.88.0" + sass-loader: "npm:16.0.5" + semver: "npm:7.7.2" source-map-loader: "npm:5.0.0" source-map-support: "npm:0.5.21" - terser: "npm:5.46.1" - tinyglobby: "npm:0.2.16" + terser: "npm:5.39.1" + tree-kill: "npm:1.2.2" tslib: "npm:2.8.1" - webpack: "npm:5.106.2" - webpack-dev-middleware: "npm:8.0.3" - webpack-dev-server: "npm:5.2.3" + webpack: "npm:5.99.8" + webpack-dev-middleware: "npm:7.4.2" + webpack-dev-server: "npm:5.2.1" webpack-merge: "npm:6.0.1" webpack-subresource-integrity: "npm:5.1.0" peerDependencies: - "@angular/compiler-cli": ^22.0.0-next.0 - "@angular/core": ^22.0.0-next.0 - "@angular/localize": ^22.0.0-next.0 - "@angular/platform-browser": ^22.0.0-next.0 - "@angular/platform-server": ^22.0.0-next.0 - "@angular/service-worker": ^22.0.0-next.0 - "@angular/ssr": ^22.0.0-next.6 + "@angular/compiler-cli": ^20.0.0 || ^20.0.0-next.0 + "@angular/core": ^20.0.0 || ^20.0.0-next.0 + "@angular/localize": ^20.0.0 || ^20.0.0-next.0 + "@angular/platform-browser": ^20.0.0 || ^20.0.0-next.0 + "@angular/platform-server": ^20.0.0 || ^20.0.0-next.0 + "@angular/service-worker": ^20.0.0 || ^20.0.0-next.0 + "@angular/ssr": ^20.0.0-rc.3 + "@web/test-runner": ^0.20.0 browser-sync: ^3.0.2 + jest: ^29.5.0 + jest-environment-jsdom: ^29.5.0 karma: ^6.3.0 - ng-packagr: ^22.0.0-next.0 + ng-packagr: ^20.0.0 || ^20.0.0-next.0 + protractor: ^7.0.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 - typescript: ">=6.0 <6.1" + typescript: ">=5.8 <5.9" dependenciesMeta: esbuild: optional: true @@ -268,111 +118,119 @@ __metadata: optional: true "@angular/ssr": optional: true + "@web/test-runner": + optional: true browser-sync: optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true karma: optional: true ng-packagr: optional: true + protractor: + optional: true tailwindcss: optional: true - checksum: 10/f8abed6b75b0a944aa303044ad403f0b55fc94bab8a164daaa1ab8ee631ec603a01bb14b46064db1945c5dfe59772f257bcbf89068b47ff5140a5261e30e8478 + checksum: 10/8122151d867e2383f21f1c2ca0b37709611fe735e671733dc74186dec53850563309f74b0d6644b98777139c9ad0cf7a22562c511cd3a3d2dcd56ef1d669d3b1 languageName: node linkType: hard -"@angular-devkit/build-webpack@npm:0.2200.0-next.6": - version: 0.2200.0-next.6 - resolution: "@angular-devkit/build-webpack@npm:0.2200.0-next.6" +"@angular-devkit/build-webpack@npm:0.2000.0-rc.3": + version: 0.2000.0-rc.3 + resolution: "@angular-devkit/build-webpack@npm:0.2000.0-rc.3" dependencies: - "@angular-devkit/architect": "npm:0.2200.0-next.6" + "@angular-devkit/architect": "npm:0.2000.0-rc.3" rxjs: "npm:7.8.2" peerDependencies: webpack: ^5.30.0 webpack-dev-server: ^5.0.2 - checksum: 10/9165c7de62f1486ae1a736a48d57ca738707849d55294a124f645395c82740456997d85c590957963fe49df32a72396490a9b7c9b320454a3742555aae4291b3 + checksum: 10/8e900633367b53e5245cd61a01ba8ebcbb7e96c6cab7a7eda377c759e2dbd21ce875488d9ad80a1c4e05590db54d30c17bd477b9f5b195613220024b9fe104d8 languageName: node linkType: hard -"@angular-devkit/core@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@angular-devkit/core@npm:22.0.0-next.6" +"@angular-devkit/core@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@angular-devkit/core@npm:20.0.0-rc.3" dependencies: - ajv: "npm:8.18.0" + ajv: "npm:8.17.1" ajv-formats: "npm:3.0.1" jsonc-parser: "npm:3.3.1" - picomatch: "npm:4.0.4" + picomatch: "npm:4.0.2" rxjs: "npm:7.8.2" - source-map: "npm:0.7.6" + source-map: "npm:0.7.4" peerDependencies: - chokidar: ^5.0.0 + chokidar: ^4.0.0 peerDependenciesMeta: chokidar: optional: true - checksum: 10/4e9c7dd683abe294a93ad3ff075c45a98bf6b263694c4ac7cf6e0542edf48d8880977b7107b8b552bc149d37d962d57c4f9741dea1e03ce42f4b81fae662fb2a + checksum: 10/1a8a3c65a95e7152059218f99dd2455734fdd18b1ce0df6d8e6fe416c207c12c99385f86b9ab04bbb5d8a5ecc83b3287aadf370a99a3a9104258a67f8167d78c languageName: node linkType: hard -"@angular-devkit/schematics@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@angular-devkit/schematics@npm:22.0.0-next.6" +"@angular-devkit/schematics@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@angular-devkit/schematics@npm:20.0.0-rc.3" dependencies: - "@angular-devkit/core": "npm:22.0.0-next.6" + "@angular-devkit/core": "npm:20.0.0-rc.3" jsonc-parser: "npm:3.3.1" - magic-string: "npm:0.30.21" - ora: "npm:9.3.0" + magic-string: "npm:0.30.17" + ora: "npm:5.4.1" rxjs: "npm:7.8.2" - checksum: 10/086da9441fa1e82a0e43799913d3db1b34f49edebd90ca43fbba51515abc153ad3a265993b3e87cb09a137a98c26e90e32ab95f430df0d6da207b6efbd9c7080 + checksum: 10/bba3d0a1de2cde81531d083f1819943279f73afe54ca313a10f27250b7211941508cf0174ab209d746fd8a79f07abd6f7fd94422511a630ac18620ebef491bc5 languageName: node linkType: hard -"@angular/build@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@angular/build@npm:22.0.0-next.6" +"@angular/build@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@angular/build@npm:20.0.0-rc.3" dependencies: "@ampproject/remapping": "npm:2.3.0" - "@angular-devkit/architect": "npm:0.2200.0-next.6" - "@babel/core": "npm:7.29.0" - "@babel/helper-annotate-as-pure": "npm:7.27.3" + "@angular-devkit/architect": "npm:0.2000.0-rc.3" + "@babel/core": "npm:7.27.1" + "@babel/helper-annotate-as-pure": "npm:7.27.1" "@babel/helper-split-export-declaration": "npm:7.24.7" - "@inquirer/confirm": "npm:6.0.12" - "@vitejs/plugin-basic-ssl": "npm:2.3.0" - beasties: "npm:0.4.2" - browserslist: "npm:^4.26.0" - esbuild: "npm:0.28.0" - https-proxy-agent: "npm:9.0.0" + "@inquirer/confirm": "npm:5.1.10" + "@vitejs/plugin-basic-ssl": "npm:2.0.0" + beasties: "npm:0.3.3" + browserslist: "npm:^4.23.0" + esbuild: "npm:0.25.4" + https-proxy-agent: "npm:7.0.6" + istanbul-lib-instrument: "npm:6.0.3" jsonc-parser: "npm:3.3.1" - listr2: "npm:10.2.1" - lmdb: "npm:3.5.4" - magic-string: "npm:0.30.21" + listr2: "npm:8.3.3" + lmdb: "npm:3.3.0" + magic-string: "npm:0.30.17" mrmime: "npm:2.0.1" - parse5-html-rewriting-stream: "npm:8.0.1" - picomatch: "npm:4.0.4" - piscina: "npm:5.1.4" - rollup: "npm:4.60.2" - sass: "npm:1.99.0" - semver: "npm:7.7.4" + parse5-html-rewriting-stream: "npm:7.1.0" + picomatch: "npm:4.0.2" + piscina: "npm:5.0.0" + rollup: "npm:4.40.2" + sass: "npm:1.88.0" + semver: "npm:7.7.2" source-map-support: "npm:0.5.21" - tinyglobby: "npm:0.2.16" - vite: "npm:7.3.2" - watchpack: "npm:2.5.1" - peerDependencies: - "@angular/compiler": ^22.0.0-next.0 - "@angular/compiler-cli": ^22.0.0-next.0 - "@angular/core": ^22.0.0-next.0 - "@angular/localize": ^22.0.0-next.0 - "@angular/platform-browser": ^22.0.0-next.0 - "@angular/platform-server": ^22.0.0-next.0 - "@angular/service-worker": ^22.0.0-next.0 - "@angular/ssr": ^22.0.0-next.6 - istanbul-lib-instrument: ^6.0.0 + tinyglobby: "npm:0.2.13" + vite: "npm:6.3.5" + watchpack: "npm:2.4.2" + peerDependencies: + "@angular/compiler": ^20.0.0 || ^20.0.0-next.0 + "@angular/compiler-cli": ^20.0.0 || ^20.0.0-next.0 + "@angular/core": ^20.0.0 || ^20.0.0-next.0 + "@angular/localize": ^20.0.0 || ^20.0.0-next.0 + "@angular/platform-browser": ^20.0.0 || ^20.0.0-next.0 + "@angular/platform-server": ^20.0.0 || ^20.0.0-next.0 + "@angular/service-worker": ^20.0.0 || ^20.0.0-next.0 + "@angular/ssr": ^20.0.0-rc.3 karma: ^6.4.0 less: ^4.2.0 - ng-packagr: ^22.0.0-next.0 + ng-packagr: ^20.0.0 || ^20.0.0-next.0 postcss: ^8.4.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 - typescript: ">=6.0 <6.1" - vitest: ^4.0.8 + typescript: ">=5.8 <5.9" + vitest: ^3.1.1 dependenciesMeta: lmdb: optional: true @@ -389,8 +247,6 @@ __metadata: optional: true "@angular/ssr": optional: true - istanbul-lib-instrument: - optional: true karma: optional: true less: @@ -403,7 +259,7 @@ __metadata: optional: true vitest: optional: true - checksum: 10/70a32a644f7e9d2b59da4d18935a29958b3762058b7c5e131ebee090d46a6e425d095db08f8d46f6e6d917b864c2d7bd06fe735dde0c4274a3086dac70082ebc + checksum: 10/bddc002248e8453d7803f2809201ad517f99e8a72ea87bdbf254d5c63db2b2f2bc0224c9b0fbf2e9da4a33e58765a879480edf58198389cf68bae7bef053398b languageName: node linkType: hard @@ -421,110 +277,102 @@ __metadata: languageName: node linkType: hard -"@angular/cli@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@angular/cli@npm:22.0.0-next.6" +"@angular/cli@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@angular/cli@npm:20.0.0-rc.3" dependencies: - "@angular-devkit/architect": "npm:0.2200.0-next.6" - "@angular-devkit/core": "npm:22.0.0-next.6" - "@angular-devkit/schematics": "npm:22.0.0-next.6" - "@inquirer/prompts": "npm:8.4.2" - "@listr2/prompt-adapter-inquirer": "npm:4.2.3" - "@modelcontextprotocol/sdk": "npm:1.29.0" - "@schematics/angular": "npm:22.0.0-next.6" + "@angular-devkit/architect": "npm:0.2000.0-rc.3" + "@angular-devkit/core": "npm:20.0.0-rc.3" + "@angular-devkit/schematics": "npm:20.0.0-rc.3" + "@inquirer/prompts": "npm:7.5.1" + "@listr2/prompt-adapter-inquirer": "npm:2.0.22" + "@schematics/angular": "npm:20.0.0-rc.3" "@yarnpkg/lockfile": "npm:1.1.0" - algoliasearch: "npm:5.50.2" - ini: "npm:6.0.0" + ini: "npm:5.0.0" jsonc-parser: "npm:3.3.1" - listr2: "npm:10.2.1" - npm-package-arg: "npm:13.0.2" - pacote: "npm:21.5.0" - parse5-html-rewriting-stream: "npm:8.0.1" - semver: "npm:7.7.4" - yargs: "npm:18.0.0" - zod: "npm:4.3.6" + listr2: "npm:8.3.3" + npm-package-arg: "npm:12.0.2" + npm-pick-manifest: "npm:10.0.0" + pacote: "npm:21.0.0" + resolve: "npm:1.22.10" + semver: "npm:7.7.2" + yargs: "npm:17.7.2" bin: ng: bin/ng.js - checksum: 10/748a3fec89b68ae5d7dcf102703ab9784addb51c7f709fe446e70bb5c2a894093d73e2d1ae6fb0a7a0406cdaff60f026078dda5ce843bae29b9a6ee4bbffcc22 + checksum: 10/d0e273ba3513a6da6017ec586ca49cd5679aca0daf402ae27f951d05d547dd96e3c356760b1d0d92d67799adf44dfd31613a1a9a54cc4352b290c646e037c76a languageName: node linkType: hard -"@angular/common@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/common@npm:22.0.0-next.9" +"@angular/common@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/common@npm:20.0.0-rc.2" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/core": 22.0.0-next.9 + "@angular/core": 20.0.0-rc.2 rxjs: ^6.5.3 || ^7.4.0 - checksum: 10/687000d367633656b7f6c771676629a7768188a6d81b4b055667b672125b89887b908a75ed1a925e78159e52b3763b2d75e64a24d1a0667e15e73e1a6ed9d4a1 + checksum: 10/7e3701ce58fb20268bf75dce0d005da7002f471e4c86ca9cf7b8be2dc848e6eba806a6622b3bc4366f5daff844524c5e54abebd71d4b9a20341d02757dbddb20 languageName: node linkType: hard -"@angular/compiler-cli@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/compiler-cli@npm:22.0.0-next.9" +"@angular/compiler-cli@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/compiler-cli@npm:20.0.0-rc.2" dependencies: - "@babel/core": "npm:7.29.0" + "@babel/core": "npm:7.27.1" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - chokidar: "npm:^5.0.0" + chokidar: "npm:^4.0.0" convert-source-map: "npm:^1.5.1" reflect-metadata: "npm:^0.2.0" semver: "npm:^7.0.0" tslib: "npm:^2.3.0" - yargs: "npm:^18.0.0" + yargs: "npm:^17.2.1" peerDependencies: - "@angular/compiler": 22.0.0-next.9 - typescript: ">=6.0 <6.1" - peerDependenciesMeta: - typescript: - optional: true + "@angular/compiler": 20.0.0-rc.2 + typescript: ">=5.8 <5.9" bin: ng-xi18n: bundles/src/bin/ng_xi18n.js ngc: bundles/src/bin/ngc.js - checksum: 10/032ecd23f4534cd1047e73aa6eb82711b52275ea43b1f93163516c77e789918789dffe52c9e5df4cb0727e0c3238c023d542e808e5f22fe7ebbcdd41501338a9 + checksum: 10/decee73b7606d3e9471674c02a788aa4f4219e02845bf6bf42ebfb900e5fcadc98afe055ad9bbe30b4dfc524f43d3c04ac7e6e04f59d9da0c80c9759ff5675eb languageName: node linkType: hard -"@angular/compiler@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/compiler@npm:22.0.0-next.9" +"@angular/compiler@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/compiler@npm:20.0.0-rc.2" dependencies: tslib: "npm:^2.3.0" - checksum: 10/2fa52e35b07bfa44773507f0da1b54b55d31d92b385ae9db0365aff7708edbde42e41e428ee4cd9e36c7053fd95cd5a4b4ccb131455242820fd713738b532671 + checksum: 10/d496b30d98ac5408d795dfb1d37a9c20858a68cca97995f7138a14ffd8b3bc5db712a4dca361f870d850285cb900cad4d3ce088540c5442c0ac7bb489ff62fa3 languageName: node linkType: hard -"@angular/core@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/core@npm:22.0.0-next.9" +"@angular/core@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/core@npm:20.0.0-rc.2" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/compiler": 22.0.0-next.9 + "@angular/compiler": 20.0.0-rc.2 rxjs: ^6.5.3 || ^7.4.0 - zone.js: ~0.15.0 || ~0.16.0 + zone.js: ~0.15.0 peerDependenciesMeta: "@angular/compiler": optional: true - zone.js: - optional: true - checksum: 10/097cbe55d1e44ea558772989ce2a57c5e92a1d443688d810d7214f2941f4a21262cecb297b90120aeca9724e3d323c277ef477f962a9daf9371a48dd88a53be9 + checksum: 10/f76bdd124b6de9c7434c74ac400b7c3b8ae21c37380bad60fef7b0c9f94d7b884cb491417a2867f8d962a5b50d9e80f85503724595c813cf1783d56e27e137d0 languageName: node linkType: hard -"@angular/forms@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/forms@npm:22.0.0-next.9" +"@angular/forms@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/forms@npm:20.0.0-rc.2" dependencies: - "@standard-schema/spec": "npm:^1.0.0" tslib: "npm:^2.3.0" peerDependencies: - "@angular/common": 22.0.0-next.9 - "@angular/core": 22.0.0-next.9 - "@angular/platform-browser": 22.0.0-next.9 + "@angular/common": 20.0.0-rc.2 + "@angular/core": 20.0.0-rc.2 + "@angular/platform-browser": 20.0.0-rc.2 rxjs: ^6.5.3 || ^7.4.0 - checksum: 10/c7f7b36b24c6e39fbee5c37ffd43e050c24aab943ddb42528a607685c14826212031fd0074be20d4aa89828403efba86fbeed923540fdc9cea2ee9f179761c8a + checksum: 10/f77dcb037e2dcc275582f4e14c5897edee70d008c28088fd275f46afc66fb29f118785411bfb694ca83de460d1fd7f7e92c3eeb505bf159f41955b783bda5fde languageName: node linkType: hard @@ -544,33 +392,33 @@ __metadata: languageName: node linkType: hard -"@angular/platform-browser@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/platform-browser@npm:22.0.0-next.9" +"@angular/platform-browser@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/platform-browser@npm:20.0.0-rc.2" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/animations": 22.0.0-next.9 - "@angular/common": 22.0.0-next.9 - "@angular/core": 22.0.0-next.9 + "@angular/animations": 20.0.0-rc.2 + "@angular/common": 20.0.0-rc.2 + "@angular/core": 20.0.0-rc.2 peerDependenciesMeta: "@angular/animations": optional: true - checksum: 10/965c8f676e6508e51fa14a696a97e0d8b030bad4cce85826b1fde4718e652fbd8de35fceab6ede1e3c5eb4fbaf1da65cac2d7e72939afa7ad343dcb61c425832 + checksum: 10/2ba5b320d03e4c84759cf88b326944ed545856c640d04dfa5b18c42367cd50ac9bf1bfdb0f6944480a7ff1d1f8d02e370063adc23ffbb461549a1a6a7ee4066b languageName: node linkType: hard -"@angular/router@npm:22.0.0-next.9": - version: 22.0.0-next.9 - resolution: "@angular/router@npm:22.0.0-next.9" +"@angular/router@npm:20.0.0-rc.2": + version: 20.0.0-rc.2 + resolution: "@angular/router@npm:20.0.0-rc.2" dependencies: tslib: "npm:^2.3.0" peerDependencies: - "@angular/common": 22.0.0-next.9 - "@angular/core": 22.0.0-next.9 - "@angular/platform-browser": 22.0.0-next.9 + "@angular/common": 20.0.0-rc.2 + "@angular/core": 20.0.0-rc.2 + "@angular/platform-browser": 20.0.0-rc.2 rxjs: ^6.5.3 || ^7.4.0 - checksum: 10/c4254461467573c7e0c72bcfc620aa121e47bb3fb4acbc4abc30cb2ec0c4ed8d949786a0c7864170d065842b5ec01950e5443d70acfa22c1c4fc353b2ef7d324 + checksum: 10/c05dc61d7ba86f9c4ceb1836e518e478a309fcb2fe130c6419abfa032759ddd40efe6bf09f03b1650b98e1c05ab25b0bccace9dcd77d73ee515788f8ea47a717 languageName: node linkType: hard @@ -596,18 +444,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/code-frame@npm:7.29.0" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.28.5" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.1.1" - checksum: 10/199e15ff89007dd30675655eec52481cb245c9fdf4f81e4dc1f866603b0217b57aff25f5ffa0a95bbc8e31eb861695330cd7869ad52cc211aa63016320ef72c5 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.26.5": +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.26.5": version: 7.26.8 resolution: "@babel/compat-data@npm:7.26.8" checksum: 10/bdddf577f670e0e12996ef37e134856c8061032edb71a13418c3d4dae8135da28910b7cd6dec6e668ab3a41e42089ef7ee9c54ef52fe0860b54cb420b0d14948 @@ -621,37 +458,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.28.6, @babel/compat-data@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/compat-data@npm:7.29.0" - checksum: 10/7f21beedb930ed8fbf7eabafc60e6e6521c1d905646bf1317a61b2163339157fe797efeb85962bf55136e166b01fd1a6b526a15974b92a8b877d564dcb6c9580 - languageName: node - linkType: hard - -"@babel/core@npm:7.29.0": - version: 7.29.0 - resolution: "@babel/core@npm:7.29.0" - dependencies: - "@babel/code-frame": "npm:^7.29.0" - "@babel/generator": "npm:^7.29.0" - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-module-transforms": "npm:^7.28.6" - "@babel/helpers": "npm:^7.28.6" - "@babel/parser": "npm:^7.29.0" - "@babel/template": "npm:^7.28.6" - "@babel/traverse": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" - "@jridgewell/remapping": "npm:^2.3.5" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10/25f4e91688cdfbaf1365831f4f245b436cdaabe63d59389b75752013b8d61819ee4257101b52fc328b0546159fd7d0e74457ed7cf12c365fea54be4fb0a40229 - languageName: node - linkType: hard - -"@babel/core@npm:^7.12.3": +"@babel/core@npm:7.27.1, @babel/core@npm:^7.12.3": version: 7.27.1 resolution: "@babel/core@npm:7.27.1" dependencies: @@ -697,16 +504,16 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:7.29.1, @babel/generator@npm:^7.29.0": - version: 7.29.1 - resolution: "@babel/generator@npm:7.29.1" +"@babel/generator@npm:7.27.1, @babel/generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/generator@npm:7.27.1" dependencies: - "@babel/parser": "npm:^7.29.0" - "@babel/types": "npm:^7.29.0" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" + "@babel/parser": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^3.0.2" - checksum: 10/61fe4ddd6e817aa312a14963ccdbb5c9a8c57e8b97b98d19a8a99ccab2215fda1a5f52bc8dd8d2e3c064497ddeb3ab8ceb55c76fa0f58f8169c34679d2256fe0 + checksum: 10/6101825922a8a116e64b507d9309b38c5bc027b333d7111fcb760422741d3c72bd8f8e5aa935c2944c434ffe376353a27afa3a25a8526dc2ef90743d266770db languageName: node linkType: hard @@ -723,25 +530,12 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.27.1": +"@babel/helper-annotate-as-pure@npm:7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.1": version: 7.27.1 - resolution: "@babel/generator@npm:7.27.1" + resolution: "@babel/helper-annotate-as-pure@npm:7.27.1" dependencies: - "@babel/parser": "npm:^7.27.1" "@babel/types": "npm:^7.27.1" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^3.0.2" - checksum: 10/6101825922a8a116e64b507d9309b38c5bc027b333d7111fcb760422741d3c72bd8f8e5aa935c2944c434ffe376353a27afa3a25a8526dc2ef90743d266770db - languageName: node - linkType: hard - -"@babel/helper-annotate-as-pure@npm:7.27.3, @babel/helper-annotate-as-pure@npm:^7.27.3": - version: 7.27.3 - resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" - dependencies: - "@babel/types": "npm:^7.27.3" - checksum: 10/63863a5c936ef82b546ca289c9d1b18fabfc24da5c4ee382830b124e2e79b68d626207febc8d4bffc720f50b2ee65691d7d12cc0308679dee2cd6bdc926b7190 + checksum: 10/3f8e4d591458d6c0621a3d670f8798b8895580214287390126e3e621ddf3df0bd07cbcc9500c2671b9ec10162c2f9feb1194da5cf039d40df8cb69d181fc0cd8 languageName: node linkType: hard @@ -754,16 +548,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-annotate-as-pure@npm:7.27.1" - dependencies: - "@babel/types": "npm:^7.27.1" - checksum: 10/3f8e4d591458d6c0621a3d670f8798b8895580214287390126e3e621ddf3df0bd07cbcc9500c2671b9ec10162c2f9feb1194da5cf039d40df8cb69d181fc0cd8 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.26.5": +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.26.5": version: 7.26.5 resolution: "@babel/helper-compilation-targets@npm:7.26.5" dependencies: @@ -776,7 +561,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.27.1": +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: @@ -789,33 +574,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-compilation-targets@npm:7.28.6" - dependencies: - "@babel/compat-data": "npm:^7.28.6" - "@babel/helper-validator-option": "npm:^7.27.1" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10/f512a5aeee4dfc6ea8807f521d085fdca8d66a7d068a6dd5e5b37da10a6081d648c0bbf66791a081e4e8e6556758da44831b331540965dfbf4f5275f3d0a8788 - languageName: node - linkType: hard - -"@babel/helper-create-class-features-plugin@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" +"@babel/helper-create-class-features-plugin@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-create-class-features-plugin@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.3" - "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-member-expression-to-functions": "npm:^7.27.1" "@babel/helper-optimise-call-expression": "npm:^7.27.1" - "@babel/helper-replace-supers": "npm:^7.28.6" + "@babel/helper-replace-supers": "npm:^7.27.1" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.6" + "@babel/traverse": "npm:^7.27.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/11f55607fcf66827ade745c0616aa3c6086aa655c0fab665dd3c4961829752e4c94c942262db30c4831ef9bce37ad444722e85ef1b7136587e28c6b1ef8ad43c + checksum: 10/701579b49046cd42f6a6b1e693e6827df8623185adf0911c4d68a219a082d8fd4501672880d92b6b96263d1c92a3beb891b3464a662a55e69e7539d8db9277da languageName: node linkType: hard @@ -845,38 +617,18 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.28.5" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.3" - regexpu-core: "npm:^6.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/d8791350fe0479af0909aa5efb6dfd3bacda743c7c3f8fa1b0bb18fe014c206505834102ee24382df1cfe5a83b4e4083220e97f420a48b2cec15bb1ad6c7c9d3 - languageName: node - linkType: hard - -"@babel/helper-define-polyfill-provider@npm:^0.6.5, @babel/helper-define-polyfill-provider@npm:^0.6.8": - version: 0.6.8 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.8" +"@babel/helper-define-polyfill-provider@npm:^0.6.3": + version: 0.6.3 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.3" dependencies: - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" - debug: "npm:^4.4.3" + "@babel/helper-compilation-targets": "npm:^7.22.6" + "@babel/helper-plugin-utils": "npm:^7.22.5" + debug: "npm:^4.1.1" lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.22.11" + resolve: "npm:^1.14.2" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/a6f9fbb82578464da35eec88c7f3e70bdd95237bfc1d3ebb9cf4536a86a577b7c6e587f9a6797b01ee08629599ee2bc6fdab39e99de505751a30d9b4877202ab - languageName: node - linkType: hard - -"@babel/helper-globals@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/helper-globals@npm:7.28.0" - checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 + checksum: 10/b79a77ac8fbf1aaf6c7f99191871760508e87d75a374ff3c39c6599a17d9bb82284797cd451769305764e504546caf22ae63367b22d6e45e32d0a8f4a34aab53 languageName: node linkType: hard @@ -890,16 +642,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" - dependencies: - "@babel/traverse": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" - checksum: 10/05e0857cf7913f03d88ca62952d3888693c21a4f4d7cfc141c630983f71fc0a64393e05cecceb7701dfe98298f7cc38fcb735d892e3c8c6f56f112c85ee1b154 - languageName: node - linkType: hard - "@babel/helper-module-imports@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-module-imports@npm:7.25.9" @@ -920,16 +662,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-module-imports@npm:7.28.6" - dependencies: - "@babel/traverse": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10/64b1380d74425566a3c288074d7ce4dea56d775d2d3325a3d4a6df1dca702916c1d268133b6f385de9ba5b822b3c6e2af5d3b11ac88e5453d5698d77264f0ec0 - languageName: node - linkType: hard - "@babel/helper-module-transforms@npm:^7.26.0": version: 7.26.0 resolution: "@babel/helper-module-transforms@npm:7.26.0" @@ -956,19 +688,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-module-transforms@npm:7.28.6" - dependencies: - "@babel/helper-module-imports": "npm:^7.28.6" - "@babel/helper-validator-identifier": "npm:^7.28.5" - "@babel/traverse": "npm:^7.28.6" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/2e421c7db743249819ee51e83054952709dc2e197c7d5d415b4bdddc718580195704bfcdf38544b3f674efc2eccd4d29a65d38678fc827ed3934a7690984cd8b - languageName: node - linkType: hard - "@babel/helper-optimise-call-expression@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" @@ -978,7 +697,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5": version: 7.26.5 resolution: "@babel/helper-plugin-utils@npm:7.26.5" checksum: 10/1cc0fd8514da3bb249bed6c27227696ab5e84289749d7258098701cffc0c599b7f61ec40dd332f8613030564b79899d9826813c96f966330bcfc7145a8377857 @@ -992,13 +711,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-plugin-utils@npm:7.28.6" - checksum: 10/21c853bbc13dbdddf03309c9a0477270124ad48989e1ad6524b83e83a77524b333f92edd2caae645c5a7ecf264ec6d04a9ebe15aeb54c7f33c037b71ec521e4a - languageName: node - linkType: hard - "@babel/helper-remap-async-to-generator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" @@ -1025,19 +737,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-replace-supers@npm:7.28.6" - dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.28.5" - "@babel/helper-optimise-call-expression": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.6" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/ad2724713a4d983208f509e9607e8f950855f11bd97518a700057eb8bec69d687a8f90dc2da0c3c47281d2e3b79cf1d14ecf1fe3e1ee0a8e90b61aee6759c9a7 - languageName: node - linkType: hard - "@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1" @@ -1085,13 +784,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10/8e5d9b0133702cfacc7f368bf792f0f8ac0483794877c6dca5fcb73810ee138e27527701826fb58a40a004f3a5ec0a2f3c3dd5e326d262530b119918f3132ba7 - languageName: node - linkType: hard - "@babel/helper-validator-option@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-option@npm:7.25.9" @@ -1137,16 +829,6 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.28.6": - version: 7.29.2 - resolution: "@babel/helpers@npm:7.29.2" - dependencies: - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" - checksum: 10/ad77706f3f917bd224e037fd0fbc67c45b240d2a45981321b093f70b7c535bee9bbddb0a19e34c362cb000ae21cdd8638f8a87a5f305a5bd7547e93fdcc524fe - languageName: node - linkType: hard - "@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.27.1, @babel/parser@npm:^7.27.2": version: 7.27.2 resolution: "@babel/parser@npm:7.27.2" @@ -1169,26 +851,15 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": - version: 7.29.2 - resolution: "@babel/parser@npm:7.29.2" - dependencies: - "@babel/types": "npm:^7.29.0" - bin: - parser: ./bin/babel-parser.js - checksum: 10/45d050bf75aa5194b3255f156173e8553d615ff5a2434674cc4a10cdc7c261931befb8618c996a1c449b87f0ef32a3407879af2ac967d95dc7b4fdbae7037efa - languageName: node - linkType: hard - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.27.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.5" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/750de98b34e6d09b545ded6e635b43cbab02fe319622964175259b98f41b16052e5931c4fbd45bad8cd0a37ebdd381233edecec9ee395b8ec51f47f47d1dbcd4 + checksum: 10/fe65257d5b82558bc6bc0f3a5a7a35b4166f71bed3747714dafb6360fadb15f036d568bc1fbeedae819165008c8feb646633ab91c0e3a95284963972f4fa9751 languageName: node linkType: hard @@ -1227,15 +898,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.6" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/traverse": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/9377897aa7cba3a0b78a7c6015799ff71504b2b203329357e42ab3185d44aab07344ba33f5dd53f14d5340c1dc5a2587346343e0859538947bbab0484e72b914 + checksum: 10/dfa68da5f68c0fa9deff1739ac270a5643ea07540b26a2a05403bc536c96595f0fe98a5eac9f9b3501b79ce57caa3045a94c75d5ccbfed946a62469a370ecdc2 languageName: node linkType: hard @@ -1248,25 +919,25 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.28.6" +"@babel/plugin-syntax-import-assertions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/25017235e1e2c4ed892aa327a3fa10f4209cc618c6dd7806fc40c07d8d7d24a39743d3d5568b8d1c8f416cffe03c174e78874ded513c9338b07a7ab1dcbab050 + checksum: 10/fb661d630808d67ecb85eabad25aac4e9696a20464bad4c4a6a0d3d40e4dc22557d47e9be3d591ec06429cf048cfe169b8891c373606344d51c4f3ac0f91d6d0 languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.28.6" +"@babel/plugin-syntax-import-attributes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/6c8c6a5988dbb9799d6027360d1a5ba64faabf551f2ef11ba4eade0c62253b5c85d44ddc8eb643c74b9acb2bcaa664a950bd5de9a5d4aef291c4f2a48223bb4b + checksum: 10/97973982fff1bbf86b3d1df13380567042887c50e2ae13a400d02a8ff2c9742a60a75e279bfb73019e1cd9710f04be5e6ab81f896e6678dcfcec8b135e8896cf languageName: node linkType: hard @@ -1293,29 +964,29 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:7.29.0, @babel/plugin-transform-async-generator-functions@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.29.0" +"@babel/plugin-transform-async-generator-functions@npm:7.27.1, @babel/plugin-transform-async-generator-functions@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-remap-async-to-generator": "npm:^7.27.1" - "@babel/traverse": "npm:^7.29.0" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e2c064a5eb212cbdf14f7c0113e069b845ca0f0ba431c1cc04607d3fc4f3bf1ed70f5c375fe7c61338a45db88bc1a79d270c8d633ce12256e1fce3666c1e6b93 + checksum: 10/92e8ba589e8b128255846375e13fee30a3b77c889578f1f30da57ee26133f397dbbc81b27e1f19c12080b096930e62bce1dcbaa7a1453d296f51eb8bda3b8d39 languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:7.28.6, @babel/plugin-transform-async-to-generator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.28.6" +"@babel/plugin-transform-async-to-generator@npm:7.27.1, @babel/plugin-transform-async-to-generator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.27.1" dependencies: - "@babel/helper-module-imports": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-remap-async-to-generator": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/bca5774263ec01dd2bf71c74bbaf7baa183bf03576636b7826c3346be70c8c8cb15cff549112f2983c36885131a0afde6c443591278c281f733ee17f455aa9b1 + checksum: 10/d79d7a7ae7d416f6a48200017d027a6ba94c09c7617eea8b4e9c803630f00094c1a4fc32bf20ce3282567824ce3fcbda51653aac4003c71ea4e681b331338979 languageName: node linkType: hard @@ -1330,90 +1001,89 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-block-scoping@npm:7.28.6" +"@babel/plugin-transform-block-scoping@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-block-scoping@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/7ab8a0856024a5360ba16c3569b739385e939bc5a15ad7d811bec8459361a9aa5ee7c5f154a4e2ce79f5d66779c19464e7532600c31a1b6f681db4eb7e1c7bde + checksum: 10/1afed217e44de8d1ad30eccd9d3ba30627798718db3c58430413ebaeb3488620bf2b16c5bfb54b1bb8935635fb5583c9dd53ea8588e9674e6c9478e7b03f94ca languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-class-properties@npm:7.28.6" +"@babel/plugin-transform-class-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-class-properties@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/200f30d44b36a768fa3a8cf690db9e333996af2ad14d9fa1b4c91a427ed9302907873b219b4ce87517ca1014a810eb2e929a6a66be68473f72b546fc64d04fbc + checksum: 10/475a6e5a9454912fe1bdc171941976ca10ea4e707675d671cdb5ce6b6761d84d1791ac61b6bca81a2e5f6430cb7b9d8e4b2392404110e69c28207a754e196294 languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-class-static-block@npm:7.28.6" +"@babel/plugin-transform-class-static-block@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-class-static-block@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10/bea7836846deefd02d9976ad1b30b5ade0d6329ecd92866db789dcf6aacfaf900b7a77031e25680f8de5ad636a771a5bdca8961361e6218d45d538ec5d9b71cc + checksum: 10/2d49de0f5ffc66ae873be1d8c3bf4d22e51889cc779d534e4dbda0f91e36907479e5c650b209fcfc80f922a6c3c2d76c905fc2f5dc78cc9a836f8c31b10686c4 languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-classes@npm:7.28.6" +"@babel/plugin-transform-classes@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-classes@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.3" - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/helper-replace-supers": "npm:^7.28.6" - "@babel/traverse": "npm:^7.28.6" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" + globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9c3278a314d1c4bcda792bb22aced20e30c735557daf9bcc56397c0f3eb54761b21c770219e4581036a10dabda3e597321ed093bc245d5f4d561e19ceff66a6d + checksum: 10/4ac2224fa68b933c80b4755300d795e055f6fb18c51432e9a4c048edcd6c64cae097eb9063d25f6c7e706ecd85a4c0b89b6f89b320b5798e3139c9cc4ff99f61 languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-computed-properties@npm:7.28.6" +"@babel/plugin-transform-computed-properties@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-computed-properties@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/template": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/template": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/4a5e270f7e1f1e9787cf7cf133d48e3c1e38eb935d29a90331a1324d7c720f589b7b626b2e6485cd5521a7a13f2dbdc89a3e46ecbe7213d5bbb631175267c4aa + checksum: 10/101f6d4575447070943d5a9efaa5bea8c552ea3083d73a9612f1a16d38b0a0a7b79a5feb65c6cc4e4fcabf28e85a570b97ccd3294da966e8fbbb6dfb97220eda languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-transform-destructuring@npm:7.28.5" +"@babel/plugin-transform-destructuring@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-destructuring@npm:7.27.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9cc67d3377bc5d8063599f2eb4588f5f9a8ab3abc9b64a40c24501fb3c1f91f4d5cf281ea9f208fd6b2ef8d9d8b018dacf1bed9493334577c966cd32370a7036 + checksum: 10/8a128e80135985a9a8f403f8c684f65c0eacb6d5295567c38b1b67dc8717821894c8a004977381c7bb82c647678521f063c981afd9d1141b25df838ad0e8c1b2 languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.28.6" +"@babel/plugin-transform-dotall-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/866ffbbdee77fa955063b37c75593db8dbbe46b1ebb64cc788ea437e3a9aa41cb7b9afcee617c678a32b6705baa0892ec8e5d4b8af3bbb0ab1b254514ccdbd37 + checksum: 10/2173e5b13f403538ffc6bd57b190cedf4caf320abc13a99e5b2721864e7148dbd3bd7c82d92377136af80432818f665fdd9a1fd33bc5549a4c91e24e5ce2413c languageName: node linkType: hard @@ -1428,15 +1098,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.29.0" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/7fa7b773259a578c9e01c80946f75ecc074520064aa7a87a65db06c7df70766e2fa6be78cda55fa9418a14e30b2b9d595484a46db48074d495d9f877a4276065 + checksum: 10/2a109613535e6ac79240dced71429e988affd6a5b3d0cd0f563c8d6c208c51ce7bf2c300bc1150502376b26a51f279119b3358f1c0f2d2f8abca3bcd62e1ae46 languageName: node linkType: hard @@ -1451,26 +1121,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-explicit-resource-management@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/plugin-transform-destructuring": "npm:^7.28.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/36d638a253dbdaee5548b4ddd21c04ee4e39914b207437bb64cf79bb41c2caadb4321768d3dba308c1016702649bc44efe751e2052de393004563c7376210d86 - languageName: node - linkType: hard - -"@babel/plugin-transform-exponentiation-operator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.28.6" +"@babel/plugin-transform-exponentiation-operator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b232152499370435c7cd4bf3321f58e189150e35ca3722ea16533d33434b97294df1342f5499671ec48e62b71c34cdea0ca8cf317ad12594a10f6fc670315e62 + checksum: 10/dbbedd24724c2d590ef59d32cb1fef34e99daba41c5b621f9f4c4da23e15c2bb4b1e3d954c314645016391404cf00f1e4ddec8f1f7891438bcde9aaf16e16ee0 languageName: node linkType: hard @@ -1510,14 +1168,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-json-strings@npm:7.28.6" +"@babel/plugin-transform-json-strings@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-json-strings@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/69d82a1a0a72ed6e6f7969e09cf330516599d79b2b4e680e9dd3c57616a8c6af049b5103456e370ab56642815e80e46ed88bb81e9e059304a85c5fe0bf137c29 + checksum: 10/2c05a02f63b49f47069271b3405a66c3c8038de5b995b0700b1bd9a5e2bb3e67abd01e4604629302a521f4d8122a4233944aefa16559fd4373d256cc5d3da57f languageName: node linkType: hard @@ -1532,14 +1190,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.28.6" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/36095d5d1cfc680e95298b5389a16016da800ae3379b130dabf557e94652c47b06610407e9fa44aaa03e9b0a5aa7b4b93348123985d44a45e369bf5f3497d149 + checksum: 10/2757955d81d65cc4701c17b83720745f6858f7a1d1d58117e379c204f47adbeb066b778596b6168bdbf4a22c229aab595d79a9abc261d0c6bfd62d4419466e73 languageName: node linkType: hard @@ -1566,29 +1224,29 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.28.6" +"@babel/plugin-transform-modules-commonjs@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ec6ea2958e778a7e0220f4a75cb5816cecddc6bd98efa10499fff7baabaa29a594d50d787a4ebf8a8ba66fefcf76ca2ded602be0b4554ae3317e53b3b3375b37 + checksum: 10/9059243a977bc1f13e3dccfc6feb6508890e7c7bb191f7eb56626b20672b4b12338051ca835ab55426875a473181502c8f35b4df58ba251bef63b25866d995fe languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.29.0" +"@babel/plugin-transform-modules-systemjs@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.27.1" dependencies: - "@babel/helper-module-transforms": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/helper-validator-identifier": "npm:^7.28.5" - "@babel/traverse": "npm:^7.29.0" + "@babel/helper-module-transforms": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b3e64728eef02d829510778226da4c06be740fe52e0d45d4aa68b24083096d8ad7df67f2e9e67198b2e85f3237d42bd66f5771f85846f7a746105d05ca2e0cae + checksum: 10/06d7bf76ac4688a36ae8e8d2dde1c3b8bab4594362132b74a00d5a32e6716944d68911b9bc53df60e59f4f9c7f1796525503ce3e3eed42f842d7775ccdfd836e languageName: node linkType: hard @@ -1604,15 +1262,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.29.0" +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/ed8c27699ca82a6c01cbfd39f3de16b90cfea4f8146a358057f76df290d308a66a8bd2e6734e6a87f68c18576e15d2d70548a84cd474d26fdf256c3f5ae44d8c + checksum: 10/a711c92d9753df26cefc1792481e5cbff4fe4f32b383d76b25e36fa865d8023b1b9aa6338cf18f5c0e864c71a7fbe8115e840872ccd61a914d9953849c68de7d languageName: node linkType: hard @@ -1627,40 +1285,39 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.28.6" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/88106952ca4f4fea8f97222a25f9595c6859d458d76905845dfa54f54e7d345e3dc338932e8c84a9c57a6c88b2f6d9ebff47130ce508a49c2b6e6a9f03858750 + checksum: 10/15333f4888ffedc449a2a21a0b1ca7983e089f43faa00cfb71d2466e20221a5fd979cdb1a3f57bc20fc62c67bd3ff3dde054133fb6324a58be8f64d20aefacd2 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.28.6" +"@babel/plugin-transform-numeric-separator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/4b5ca60e481e22f0842761a3badca17376a230b5a7e5482338604eb95836c2d0c9c9bde53bdc5c2de1c6a12ae6c12de7464d098bf74b0943f85905ca358f0b68 + checksum: 10/049b958911de86d32408cd78017940a207e49c054ae9534ab53a32a57122cc592c0aae3c166d6f29bd1a7d75cc779d71883582dd76cb28b2fbb493e842d8ffca languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.6" +"@babel/plugin-transform-object-rest-spread@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.27.2" dependencies: - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/plugin-transform-destructuring": "npm:^7.28.5" - "@babel/plugin-transform-parameters": "npm:^7.27.7" - "@babel/traverse": "npm:^7.28.6" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.27.1" + "@babel/plugin-transform-parameters": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9c8c51a515a5ec98a33a715e82d49f873e58b04b53fa1e826f3c2009f7133cd396d6730553a53d265e096dbfbea17dd100ae38815d0b506c094cb316a7a5519e + checksum: 10/284a458f229e2acabe8a4d3a6cb1cb510dd1d76dc652d220865b8008b4cad923cea5faf76a8990810c2f99704797fbebb2ff15d482c448193691985a71226ecf languageName: node linkType: hard @@ -1676,14 +1333,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.28.6" +"@babel/plugin-transform-optional-catch-binding@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ee24a17defec056eb9ef01824d7e4a1f65d531af6b4b79acfd0bcb95ce0b47926e80c61897f36f8c01ce733b069c9acdb1c9ce5ec07a729d0dbf9e8d859fe992 + checksum: 10/f4356b04cf21a98480f9788ea50f1f13ee88e89bb6393ba4b84d1f39a4a84c7928c9a4328e8f4c5b6deb218da68a8fd17bf4f46faec7653ddc20ffaaa5ba49f4 languageName: node linkType: hard @@ -1699,51 +1356,39 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/c7cf29f99384a9a98748f04489a122c0106e0316aa64a2e61ef8af74c1057b587b96d9a08eb4e33d2ac17d1aaff1f0a86fae658d429fa7bcce4ef977e0ad684b - languageName: node - linkType: hard - -"@babel/plugin-transform-parameters@npm:^7.27.7": - version: 7.27.7 - resolution: "@babel/plugin-transform-parameters@npm:7.27.7" +"@babel/plugin-transform-parameters@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-parameters@npm:7.27.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/ba0aa8c977a03bf83030668f64c1d721e4e82d8cce89cdde75a2755862b79dbe9e7f58ca955e68c721fd494d6ee3826e46efad3fbf0855fcc92cb269477b4777 + checksum: 10/47db574f8f3adf7a5d85933c9a2a2dee956ceda9e00fb4e03e9a9d600b559f06cba2da7c5e78a12b05dcf993cf147634edf0391f3f20a6b451830f41be47fe68 languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-private-methods@npm:7.28.6" +"@babel/plugin-transform-private-methods@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-private-methods@npm:7.27.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b80179b28f6a165674d0b0d6c6349b13a01dd282b18f56933423c0a33c23fc0626c8f011f859fc20737d021fe966eb8474a5233e4596401482e9ee7fb00e2aa2 + checksum: 10/c76f8f6056946466116e67eb9d8014a2d748ade2062636ab82045c1dac9c233aff10e597777bc5af6f26428beb845ceb41b95007abef7d0484da95789da56662 languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.28.6" +"@babel/plugin-transform-private-property-in-object@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.27.1" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.3" - "@babel/helper-create-class-features-plugin": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-annotate-as-pure": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d02008c62fd32ff747b850b8581ab5076b717320e1cb01c7fc66ebf5169095bd922e18cfb269992f85bc7fbd2cc61e5b5af25e2b54aad67411474b789ea94d5f + checksum: 10/d4466d42a02c5a318d9d7b8102969fd032b17ff044918dfd462d5cc49bd11f5773ee0794781702afdf4727ba11e9be6cbea1e396bc0a7307761bb9a56399012a languageName: node linkType: hard @@ -1758,26 +1403,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-regenerator@npm:7.29.0" +"@babel/plugin-transform-regenerator@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-regenerator@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c8fa9da74371568c5d34fd7d53de018752550cb10334040ca59e41f34b27f127974bdc5b4d1a1a8e8f3ebcf3cb7f650aa3f2df3b7bf1b7edf67c04493b9e3cb8 + checksum: 10/a6a3f818e4cc5ac5a01962a1d66515cb6791f32eb1dcd810dbb39f6e580c0c96b47fcc5c5633137e040f4e137e40e6f4dad8a3d57d49b15aed40e72e13e30d93 languageName: node linkType: hard -"@babel/plugin-transform-regexp-modifiers@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.28.6" +"@babel/plugin-transform-regexp-modifiers@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/5aacc570034c085afa0165137bb9a04cd4299b86eb9092933a96dcc1132c8f591d9d534419988f5f762b2f70d43a3c719a6b8fa05fdd3b2b1820d01cf85500da + checksum: 10/f6cb385fe0e798bff7e9b20cf5912bf40e180895ff3610b1ccdce260f3c20daaebb3a99dc087c8168a99151cd3e16b94f4689fd5a4b01cf1834b45c133e620b2 languageName: node linkType: hard @@ -1792,19 +1437,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-runtime@npm:7.29.0": - version: 7.29.0 - resolution: "@babel/plugin-transform-runtime@npm:7.29.0" +"@babel/plugin-transform-runtime@npm:7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-runtime@npm:7.27.1" dependencies: - "@babel/helper-module-imports": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" - babel-plugin-polyfill-corejs2: "npm:^0.4.14" - babel-plugin-polyfill-corejs3: "npm:^0.13.0" - babel-plugin-polyfill-regenerator: "npm:^0.6.5" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" + babel-plugin-polyfill-corejs2: "npm:^0.4.10" + babel-plugin-polyfill-corejs3: "npm:^0.11.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/314cfede923a7fb3aeecf4b282a3090e4a9ae1d84005e9a0365284c5142165a4dccd308455af9013d486a4ad8ada25ccad2fea28c2ec19b086d1ffa0088a69d7 + checksum: 10/a9f0f6a1b392873a7e0269560330e3fc6f3c078948e2f96d255bbda32f7040021e6cc253f7e2430c9ce8aec237063730dc8aa7f3d29ecb6e090ade321fb8b062 languageName: node linkType: hard @@ -1819,15 +1464,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-spread@npm:7.28.6" +"@babel/plugin-transform-spread@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-spread@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1fa02ac60ae5e49d46fa2966aaf3f7578cf37255534c2ecf379d65855088a1623c3eea28b9ee6a0b1413b0199b51f9019d0da3fe9da89986bc47e07242415f60 + checksum: 10/3edd28b07e1951f32aa2d380d9a0e0ed408c64a5cea2921d02308541042aca18f146b3a61e82e534d4d61cb3225dbc847f4f063aedfff6230b1a41282e95e8a2 languageName: node linkType: hard @@ -1875,15 +1520,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.28.6" +"@babel/plugin-transform-unicode-property-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/d14e8c51aa73f592575c1543400fd67d96df6410d75c9dc10dd640fd7eecb37366a2f2368bbdd7529842532eda4af181c921bda95146c6d373c64ea59c6e9991 + checksum: 10/5d99c89537d1ebaac3f526c04b162cf95a47d363d4829f78c6701a2c06ab78a48da66a94f853f85f44a3d72153410ba923e072bed4b7166fa097f503eb14131d languageName: node linkType: hard @@ -1899,95 +1544,94 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.28.6" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.27.1" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-create-regexp-features-plugin": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/423971fe2eef9d18782b1c30f5f42613ee510e5b9c08760c5538a0997b36c34495acce261e0e37a27831f81330359230bd1f33c2e1822de70241002b45b7d68e + checksum: 10/295126074c7388ab05c82ef3ed0907a1ee4666bbdd763477ead9aba6eb2c74bdf65669416861ac93d337a4a27640963bb214acadc2697275ce95aab14868d57f languageName: node linkType: hard -"@babel/preset-env@npm:7.29.2": - version: 7.29.2 - resolution: "@babel/preset-env@npm:7.29.2" +"@babel/preset-env@npm:7.27.2": + version: 7.27.2 + resolution: "@babel/preset-env@npm:7.27.2" dependencies: - "@babel/compat-data": "npm:^7.29.0" - "@babel/helper-compilation-targets": "npm:^7.28.6" - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.28.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.27.1" "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.27.1" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.27.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.28.6" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.27.1" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions": "npm:^7.28.6" - "@babel/plugin-syntax-import-attributes": "npm:^7.28.6" + "@babel/plugin-syntax-import-assertions": "npm:^7.27.1" + "@babel/plugin-syntax-import-attributes": "npm:^7.27.1" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" - "@babel/plugin-transform-async-generator-functions": "npm:^7.29.0" - "@babel/plugin-transform-async-to-generator": "npm:^7.28.6" + "@babel/plugin-transform-async-generator-functions": "npm:^7.27.1" + "@babel/plugin-transform-async-to-generator": "npm:^7.27.1" "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" - "@babel/plugin-transform-block-scoping": "npm:^7.28.6" - "@babel/plugin-transform-class-properties": "npm:^7.28.6" - "@babel/plugin-transform-class-static-block": "npm:^7.28.6" - "@babel/plugin-transform-classes": "npm:^7.28.6" - "@babel/plugin-transform-computed-properties": "npm:^7.28.6" - "@babel/plugin-transform-destructuring": "npm:^7.28.5" - "@babel/plugin-transform-dotall-regex": "npm:^7.28.6" + "@babel/plugin-transform-block-scoping": "npm:^7.27.1" + "@babel/plugin-transform-class-properties": "npm:^7.27.1" + "@babel/plugin-transform-class-static-block": "npm:^7.27.1" + "@babel/plugin-transform-classes": "npm:^7.27.1" + "@babel/plugin-transform-computed-properties": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.27.1" + "@babel/plugin-transform-dotall-regex": "npm:^7.27.1" "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.29.0" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.27.1" "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" - "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.6" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.28.6" + "@babel/plugin-transform-exponentiation-operator": "npm:^7.27.1" "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" "@babel/plugin-transform-for-of": "npm:^7.27.1" "@babel/plugin-transform-function-name": "npm:^7.27.1" - "@babel/plugin-transform-json-strings": "npm:^7.28.6" + "@babel/plugin-transform-json-strings": "npm:^7.27.1" "@babel/plugin-transform-literals": "npm:^7.27.1" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.28.6" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.27.1" "@babel/plugin-transform-member-expression-literals": "npm:^7.27.1" "@babel/plugin-transform-modules-amd": "npm:^7.27.1" - "@babel/plugin-transform-modules-commonjs": "npm:^7.28.6" - "@babel/plugin-transform-modules-systemjs": "npm:^7.29.0" + "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" + "@babel/plugin-transform-modules-systemjs": "npm:^7.27.1" "@babel/plugin-transform-modules-umd": "npm:^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.29.0" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.27.1" "@babel/plugin-transform-new-target": "npm:^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.28.6" - "@babel/plugin-transform-numeric-separator": "npm:^7.28.6" - "@babel/plugin-transform-object-rest-spread": "npm:^7.28.6" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.27.1" + "@babel/plugin-transform-numeric-separator": "npm:^7.27.1" + "@babel/plugin-transform-object-rest-spread": "npm:^7.27.2" "@babel/plugin-transform-object-super": "npm:^7.27.1" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.28.6" - "@babel/plugin-transform-optional-chaining": "npm:^7.28.6" - "@babel/plugin-transform-parameters": "npm:^7.27.7" - "@babel/plugin-transform-private-methods": "npm:^7.28.6" - "@babel/plugin-transform-private-property-in-object": "npm:^7.28.6" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" + "@babel/plugin-transform-parameters": "npm:^7.27.1" + "@babel/plugin-transform-private-methods": "npm:^7.27.1" + "@babel/plugin-transform-private-property-in-object": "npm:^7.27.1" "@babel/plugin-transform-property-literals": "npm:^7.27.1" - "@babel/plugin-transform-regenerator": "npm:^7.29.0" - "@babel/plugin-transform-regexp-modifiers": "npm:^7.28.6" + "@babel/plugin-transform-regenerator": "npm:^7.27.1" + "@babel/plugin-transform-regexp-modifiers": "npm:^7.27.1" "@babel/plugin-transform-reserved-words": "npm:^7.27.1" "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" - "@babel/plugin-transform-spread": "npm:^7.28.6" + "@babel/plugin-transform-spread": "npm:^7.27.1" "@babel/plugin-transform-sticky-regex": "npm:^7.27.1" "@babel/plugin-transform-template-literals": "npm:^7.27.1" "@babel/plugin-transform-typeof-symbol": "npm:^7.27.1" "@babel/plugin-transform-unicode-escapes": "npm:^7.27.1" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.28.6" + "@babel/plugin-transform-unicode-property-regex": "npm:^7.27.1" "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.28.6" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.27.1" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.15" - babel-plugin-polyfill-corejs3: "npm:^0.14.0" - babel-plugin-polyfill-regenerator: "npm:^0.6.6" - core-js-compat: "npm:^3.48.0" + babel-plugin-polyfill-corejs2: "npm:^0.4.10" + babel-plugin-polyfill-corejs3: "npm:^0.11.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.1" + core-js-compat: "npm:^3.40.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/25a2dd82483d0f5bc781a939cebf502b80415d057806c87073f00f9a943c440b9862a265ca445ea1cba1fa79ee6361d05485465cdfc7797a0ec6d6493cf5d95b + checksum: 10/3748b5e5582bee12f2b21ee4af9552a0ea8851fdfa8e54cdab142ac9191b7e9b1673d23056c0d2c3c6fd554eb85873664acfc9829c4f14a8ae7676548184eff6 languageName: node linkType: hard @@ -2004,10 +1648,10 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.29.2": - version: 7.29.2 - resolution: "@babel/runtime@npm:7.29.2" - checksum: 10/f55ba4052aa0255055b34371a145fbe69c29b37b49eaea14805b095bfb4153701486416e89392fd27ec8abafa53868be86e960b9f8f959fff91f2c8ac2a14b02 +"@babel/runtime@npm:7.27.1": + version: 7.27.1 + resolution: "@babel/runtime@npm:7.27.1" + checksum: 10/34cefcbf781ea5a4f1b93f8563327b9ac82694bebdae91e8bd9d7f58d084cbe5b9a6e7f94d77076e15b0bcdaa0040a36cb30737584028df6c4673b4c67b2a31d languageName: node linkType: hard @@ -2033,17 +1677,6 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/template@npm:7.28.6" - dependencies: - "@babel/code-frame": "npm:^7.28.6" - "@babel/parser": "npm:^7.28.6" - "@babel/types": "npm:^7.28.6" - checksum: 10/0ad6e32bf1e7e31bf6b52c20d15391f541ddd645cbd488a77fe537a15b280ee91acd3a777062c52e03eedbc2e1f41548791f6a3697c02476ec5daf49faa38533 - languageName: node - linkType: hard - "@babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.9": version: 7.26.9 resolution: "@babel/traverse@npm:7.26.9" @@ -2074,21 +1707,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/traverse@npm:7.29.0" - dependencies: - "@babel/code-frame": "npm:^7.29.0" - "@babel/generator": "npm:^7.29.0" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.29.0" - "@babel/template": "npm:^7.28.6" - "@babel/types": "npm:^7.29.0" - debug: "npm:^4.3.1" - checksum: 10/3a0d0438f1ba9fed4fbe1706ea598a865f9af655a16ca9517ab57bda526e224569ca1b980b473fb68feea5e08deafbbf2cf9febb941f92f2d2533310c3fc4abc - languageName: node - linkType: hard - "@babel/types@npm:^7.24.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.9, @babel/types@npm:^7.4.4": version: 7.26.9 resolution: "@babel/types@npm:7.26.9" @@ -2109,16 +1727,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.27.3, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0": - version: 7.29.0 - resolution: "@babel/types@npm:7.29.0" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10/bfc2b211210f3894dcd7e6a33b2d1c32c93495dc1e36b547376aa33441abe551ab4bc1640d4154ee2acd8e46d3bbc925c7224caae02fcaf0e6a771e97fccc661 - languageName: node - linkType: hard - "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -2126,638 +1734,594 @@ __metadata: languageName: node linkType: hard -"@discoveryjs/json-ext@npm:1.0.0": - version: 1.0.0 - resolution: "@discoveryjs/json-ext@npm:1.0.0" - checksum: 10/18e55d22132ea2b2088966a87ad6a80b5806e6d92c8ff7f3e2284fafa6b5d92060373145ddaf25593f9b2fcdbbe1587de18afe62ee4137d7ab02cb30a079d6a1 +"@discoveryjs/json-ext@npm:0.6.3": + version: 0.6.3 + resolution: "@discoveryjs/json-ext@npm:0.6.3" + checksum: 10/6cb35ce92c8f1e9533250da9a893def63cce4f9a4f67677259bf11619d83858ca9c010171f49b22d83153b7b7ff65c39bbbf0edf4734d67e864de1044b7a943c languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/aix-ppc64@npm:0.27.7" +"@esbuild/aix-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/aix-ppc64@npm:0.25.0" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/aix-ppc64@npm:0.28.0" +"@esbuild/aix-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/aix-ppc64@npm:0.25.4" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/android-arm64@npm:0.27.7" +"@esbuild/android-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm64@npm:0.25.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/android-arm64@npm:0.28.0" +"@esbuild/android-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm64@npm:0.25.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/android-arm@npm:0.27.7" +"@esbuild/android-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm@npm:0.25.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-arm@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/android-arm@npm:0.28.0" +"@esbuild/android-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm@npm:0.25.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/android-x64@npm:0.27.7" +"@esbuild/android-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-x64@npm:0.25.0" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/android-x64@npm:0.28.0" +"@esbuild/android-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-x64@npm:0.25.4" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/darwin-arm64@npm:0.27.7" +"@esbuild/darwin-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-arm64@npm:0.25.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/darwin-arm64@npm:0.28.0" +"@esbuild/darwin-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-arm64@npm:0.25.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/darwin-x64@npm:0.27.7" +"@esbuild/darwin-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-x64@npm:0.25.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/darwin-x64@npm:0.28.0" +"@esbuild/darwin-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-x64@npm:0.25.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/freebsd-arm64@npm:0.27.7" +"@esbuild/freebsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-arm64@npm:0.25.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/freebsd-arm64@npm:0.28.0" +"@esbuild/freebsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-arm64@npm:0.25.4" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/freebsd-x64@npm:0.27.7" +"@esbuild/freebsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-x64@npm:0.25.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/freebsd-x64@npm:0.28.0" +"@esbuild/freebsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-x64@npm:0.25.4" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-arm64@npm:0.27.7" +"@esbuild/linux-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm64@npm:0.25.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-arm64@npm:0.28.0" +"@esbuild/linux-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm64@npm:0.25.4" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-arm@npm:0.27.7" +"@esbuild/linux-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm@npm:0.25.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-arm@npm:0.28.0" +"@esbuild/linux-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm@npm:0.25.4" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-ia32@npm:0.27.7" +"@esbuild/linux-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ia32@npm:0.25.0" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-ia32@npm:0.28.0" +"@esbuild/linux-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ia32@npm:0.25.4" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-loong64@npm:0.27.7" +"@esbuild/linux-loong64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-loong64@npm:0.25.0" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-loong64@npm:0.28.0" +"@esbuild/linux-loong64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-loong64@npm:0.25.4" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-mips64el@npm:0.27.7" +"@esbuild/linux-mips64el@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-mips64el@npm:0.25.0" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-mips64el@npm:0.28.0" +"@esbuild/linux-mips64el@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-mips64el@npm:0.25.4" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-ppc64@npm:0.27.7" +"@esbuild/linux-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ppc64@npm:0.25.0" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-ppc64@npm:0.28.0" +"@esbuild/linux-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ppc64@npm:0.25.4" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-riscv64@npm:0.27.7" +"@esbuild/linux-riscv64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-riscv64@npm:0.25.0" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-riscv64@npm:0.28.0" +"@esbuild/linux-riscv64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-riscv64@npm:0.25.4" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-s390x@npm:0.27.7" +"@esbuild/linux-s390x@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-s390x@npm:0.25.0" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-s390x@npm:0.28.0" +"@esbuild/linux-s390x@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-s390x@npm:0.25.4" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/linux-x64@npm:0.27.7" +"@esbuild/linux-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-x64@npm:0.25.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/linux-x64@npm:0.28.0" +"@esbuild/linux-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-x64@npm:0.25.4" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/netbsd-arm64@npm:0.27.7" +"@esbuild/netbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-arm64@npm:0.25.0" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/netbsd-arm64@npm:0.28.0" +"@esbuild/netbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-arm64@npm:0.25.4" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/netbsd-x64@npm:0.27.7" +"@esbuild/netbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-x64@npm:0.25.0" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/netbsd-x64@npm:0.28.0" +"@esbuild/netbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-x64@npm:0.25.4" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/openbsd-arm64@npm:0.27.7" +"@esbuild/openbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-arm64@npm:0.25.0" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/openbsd-arm64@npm:0.28.0" +"@esbuild/openbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-arm64@npm:0.25.4" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/openbsd-x64@npm:0.27.7" +"@esbuild/openbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-x64@npm:0.25.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/openbsd-x64@npm:0.28.0" +"@esbuild/openbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-x64@npm:0.25.4" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/openharmony-arm64@npm:0.27.7" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/openharmony-arm64@npm:0.28.0" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/sunos-x64@npm:0.27.7" +"@esbuild/sunos-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/sunos-x64@npm:0.25.0" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/sunos-x64@npm:0.28.0" +"@esbuild/sunos-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/sunos-x64@npm:0.25.4" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/win32-arm64@npm:0.27.7" +"@esbuild/win32-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-arm64@npm:0.25.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/win32-arm64@npm:0.28.0" +"@esbuild/win32-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-arm64@npm:0.25.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/win32-ia32@npm:0.27.7" +"@esbuild/win32-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-ia32@npm:0.25.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/win32-ia32@npm:0.28.0" +"@esbuild/win32-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-ia32@npm:0.25.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.27.7": - version: 0.27.7 - resolution: "@esbuild/win32-x64@npm:0.27.7" +"@esbuild/win32-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-x64@npm:0.25.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.28.0": - version: 0.28.0 - resolution: "@esbuild/win32-x64@npm:0.28.0" +"@esbuild/win32-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-x64@npm:0.25.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@gar/promise-retry@npm:^1.0.0, @gar/promise-retry@npm:^1.0.2": - version: 1.0.3 - resolution: "@gar/promise-retry@npm:1.0.3" - checksum: 10/0d13ea3bb1025755e055648f6e290d2a7e0c87affaf552218f09f66b3fcd9ea9d5c9cc5fe2aa6e285e1530437768e40f9448fe9a86f4f3417b216dcf488d3d1a - languageName: node - linkType: hard - -"@harperfast/extended-iterable@npm:^1.0.3": - version: 1.0.3 - resolution: "@harperfast/extended-iterable@npm:1.0.3" - checksum: 10/39ccf64e0358bb37e43b03a9392a708d3acdc2f2f71a95009a8d21e5203de1fe32b7eb8ff64b4d4dcbbc005e976226d4360cfced091018d618f26e5a94344110 - languageName: node - linkType: hard - -"@hono/node-server@npm:^1.19.9": - version: 1.19.14 - resolution: "@hono/node-server@npm:1.19.14" - peerDependencies: - hono: ^4 - checksum: 10/618dd95feeb3fd11ec8502e088879cd86529523788de19602edebd16892dd61899e73564d6e3d00875cc5a49488a908ddb2aa425d28f9cdeb7f22cfecabf022c - languageName: node - linkType: hard - -"@inquirer/ansi@npm:^2.0.5": - version: 2.0.5 - resolution: "@inquirer/ansi@npm:2.0.5" - checksum: 10/482f8a606885ee0377a60eb5e9b303ae75fcfb2c6250819be348047c89e4e01a25feef369d3646dec7ba17e38cd5cc08271db6db21c401be315b3ada749e6b53 - languageName: node - linkType: hard - -"@inquirer/checkbox@npm:^5.1.4": - version: 5.1.4 - resolution: "@inquirer/checkbox@npm:5.1.4" +"@inquirer/checkbox@npm:^4.1.6": + version: 4.1.6 + resolution: "@inquirer/checkbox@npm:4.1.6" dependencies: - "@inquirer/ansi": "npm:^2.0.5" - "@inquirer/core": "npm:^11.1.9" - "@inquirer/figures": "npm:^2.0.5" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/figures": "npm:^1.0.11" + "@inquirer/type": "npm:^3.0.6" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/1031846424338c293e6b77526c67b5c5d3dfbe4a12c0c7e8f993f376f99a7cbb6e9b5c993706177aa05130dbebb2e84b0408bc2dbc3026ade8250f2e36a9ea90 + checksum: 10/28012e16e72393ad6cc5b659620685a75e3e0227c3a2c6d6d1b235742ed7cae0516479e0e1b974c002b8fc7bf49698e9af2900a22cc5b1a83257d9000802401b languageName: node linkType: hard -"@inquirer/confirm@npm:6.0.12, @inquirer/confirm@npm:^6.0.12": - version: 6.0.12 - resolution: "@inquirer/confirm@npm:6.0.12" +"@inquirer/confirm@npm:5.1.10, @inquirer/confirm@npm:^5.1.10": + version: 5.1.10 + resolution: "@inquirer/confirm@npm:5.1.10" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/7195a02074b29c7562fd574b80ca1caa9a177fedb830f8d13831cb4498df7c8252862b9f0d964118c2bf139faffd78c9e0ecaad0972d6c02323f5f6efc7d408b + checksum: 10/d2972697eb14c4753745fdb6e8087d2b08bfde947e615d2380260c4430d54fdd243d78b033cc948011e41b30ac46da5d5b37ee4c19a0fa28f54cf34781a2ef99 languageName: node linkType: hard -"@inquirer/core@npm:^11.1.9": - version: 11.1.9 - resolution: "@inquirer/core@npm:11.1.9" +"@inquirer/core@npm:^10.1.11": + version: 10.1.11 + resolution: "@inquirer/core@npm:10.1.11" dependencies: - "@inquirer/ansi": "npm:^2.0.5" - "@inquirer/figures": "npm:^2.0.5" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/figures": "npm:^1.0.11" + "@inquirer/type": "npm:^3.0.6" + ansi-escapes: "npm:^4.3.2" cli-width: "npm:^4.1.0" - fast-wrap-ansi: "npm:^0.2.0" - mute-stream: "npm:^3.0.0" + mute-stream: "npm:^2.0.0" signal-exit: "npm:^4.1.0" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/999949afced2c4c5145ad3c41293788cb23ad59209da826625212c0911e965ad3b504f4ae2b80887475e1029df968775a025ec3774434f9e371f58472447000e + checksum: 10/3a9ccc4128511d4aeb96946b6919e8a0a06f7ea820649dde26ce366d862a5d60b3f22b93dce4b1888304cf5f7c80e9c70701003c58bd981a76f8b308379723fa languageName: node linkType: hard -"@inquirer/editor@npm:^5.1.1": - version: 5.1.1 - resolution: "@inquirer/editor@npm:5.1.1" +"@inquirer/editor@npm:^4.2.11": + version: 4.2.11 + resolution: "@inquirer/editor@npm:4.2.11" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/external-editor": "npm:^3.0.0" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" + external-editor: "npm:^3.1.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/3f95eb0635bd7ea9687979cc34ce9438152faf876f3a541b4e1be79c318725ed1ed33185ee466653363c81b30929ccaa8c74e00707d6dc974b76524aeb2b5a27 + checksum: 10/dcc65e6dc2cf25fd03939b54ff195521748114d3d2986296d708b4357d48d9ac5843e9774b1d02e0f77b9b0edbf4c8b10a77edd99910e1833864b379e5b66ced languageName: node linkType: hard -"@inquirer/expand@npm:^5.0.13": - version: 5.0.13 - resolution: "@inquirer/expand@npm:5.0.13" +"@inquirer/expand@npm:^4.0.13": + version: 4.0.13 + resolution: "@inquirer/expand@npm:4.0.13" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/6a7335a5689c5eb349a84e9752cfb92eafb9ac1c525a7ce5d79dbadefed39e20cc2f68251a0c080091dc76fda4d5fb5788ee5777ebdcdeb980ad263817ee796d + checksum: 10/25ac3a84dbd0b7763aa85ce75c9f3d2022bcc307973a5a3e0b538e2c1e2a94b5eef0b786536589e5f1554a7654853887d150c80b66e3335cc831aa0a5e7d088a languageName: node linkType: hard -"@inquirer/external-editor@npm:^3.0.0": - version: 3.0.0 - resolution: "@inquirer/external-editor@npm:3.0.0" +"@inquirer/figures@npm:^1.0.11": + version: 1.0.11 + resolution: "@inquirer/figures@npm:1.0.11" + checksum: 10/357ddd2e83718bc3c9189d518b93fd69099af9c860354df9a5ac0ec024cb5df1228ae4608d2de7625624d2adcd047db813f29426a610eaae7b9e449f8c753c6b + languageName: node + linkType: hard + +"@inquirer/input@npm:^4.1.10": + version: 4.1.10 + resolution: "@inquirer/input@npm:4.1.10" dependencies: - chardet: "npm:^2.1.1" - iconv-lite: "npm:^0.7.2" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/a2b0a255601f563317c21547778fb081d0356de478ffa70eb29a9e2247761a76b97fb7f50dcc5e1e3cafb2f888f3ac684374c35f929d1f8b280361c6c66c97d0 - languageName: node - linkType: hard - -"@inquirer/figures@npm:^2.0.5": - version: 2.0.5 - resolution: "@inquirer/figures@npm:2.0.5" - checksum: 10/e4d09c11a75206578abcfd8fc69b0f54cff7a853826696df5b3a45ed24ebc5c82e8998f1e9fa42119de848e6a0a526a6ac476053800413637bf6d21c2116cc60 + checksum: 10/61ea42f1171fc0113bfde9fd5b5a32a6f436011178fa08613685f337b3f3cb1bc60b1a76b3ab55fc2c895d87196526add2e1b0711249d539eb982428878566f2 languageName: node linkType: hard -"@inquirer/input@npm:^5.0.12": - version: 5.0.12 - resolution: "@inquirer/input@npm:5.0.12" +"@inquirer/number@npm:^3.0.13": + version: 3.0.13 + resolution: "@inquirer/number@npm:3.0.13" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/2a0c987442b0214eca2c80d3a9c99721bd9229ddf814710a5d719f76d2fb846afb4fee551c1affdfa8ef6e40b3453f984090732377715c7cc9dd7fa7c416a1a0 + checksum: 10/6df930d3ef281dff5b6b59fbdc999bcfeaf49175e2a1739f9db80a4e10b10060045cb265fb2737c8382d8264f457ab2f647c20368e288562068d2bba36fdca54 languageName: node linkType: hard -"@inquirer/number@npm:^4.0.12": - version: 4.0.12 - resolution: "@inquirer/number@npm:4.0.12" +"@inquirer/password@npm:^4.0.13": + version: 4.0.13 + resolution: "@inquirer/password@npm:4.0.13" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" + ansi-escapes: "npm:^4.3.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/2315c7ba170a116510eb9f0325ba4be84c4a01375cfeaa2d313264e47ece8004a7626ec6cf601c04bc9942931e4f7ed6a6879366ec4fdf8d73d86ef81b43a5bb + checksum: 10/f45f51e12326586b205195f5b669ce6f529b7f0bfad9ab667fb90bd4858c86d9bfb372310e0deb7f600fccf2577bd3a992feaf3fcfbc86b86e715878c4ed52e5 languageName: node linkType: hard -"@inquirer/password@npm:^5.0.12": - version: 5.0.12 - resolution: "@inquirer/password@npm:5.0.12" +"@inquirer/prompts@npm:7.5.1": + version: 7.5.1 + resolution: "@inquirer/prompts@npm:7.5.1" dependencies: - "@inquirer/ansi": "npm:^2.0.5" - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/checkbox": "npm:^4.1.6" + "@inquirer/confirm": "npm:^5.1.10" + "@inquirer/editor": "npm:^4.2.11" + "@inquirer/expand": "npm:^4.0.13" + "@inquirer/input": "npm:^4.1.10" + "@inquirer/number": "npm:^3.0.13" + "@inquirer/password": "npm:^4.0.13" + "@inquirer/rawlist": "npm:^4.1.1" + "@inquirer/search": "npm:^3.0.13" + "@inquirer/select": "npm:^4.2.1" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/feca5d674530c7cb05a7056de066edcfd966fc0dffee90c9fb60b82b121523550d430af00462fe332d150fabfc7431e1ee2bce006bc31ba249f94e8daa98c71e + checksum: 10/febb8a1bb6e7ff63b0e6c88ac9af7f7a2daf621f80c0e720cc7a68bd9fa99c7253911271d547ba3b55f18b580298a58440f3f45c974b8e895cfae929fadec868 languageName: node linkType: hard -"@inquirer/prompts@npm:8.4.2": - version: 8.4.2 - resolution: "@inquirer/prompts@npm:8.4.2" +"@inquirer/rawlist@npm:^4.1.1": + version: 4.1.1 + resolution: "@inquirer/rawlist@npm:4.1.1" dependencies: - "@inquirer/checkbox": "npm:^5.1.4" - "@inquirer/confirm": "npm:^6.0.12" - "@inquirer/editor": "npm:^5.1.1" - "@inquirer/expand": "npm:^5.0.13" - "@inquirer/input": "npm:^5.0.12" - "@inquirer/number": "npm:^4.0.12" - "@inquirer/password": "npm:^5.0.12" - "@inquirer/rawlist": "npm:^5.2.8" - "@inquirer/search": "npm:^4.1.8" - "@inquirer/select": "npm:^5.1.4" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/type": "npm:^3.0.6" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/96d7b01ca9961554e1739c60c82a2162233f246c2b401cff1b1376861e7e732814475aa20bcbe74014a4a8bda7872a907f9c8d9beb034f68d900e04606b32f48 + checksum: 10/e7c272f9f7a1576c9c1212a278c2d4bad7b394ddf512d3bbbf75902baa7a4fe4bde1b707f1d4c0cbe3963d0ba5a92e7fcbc4dffbb817ecec9b4fa70ac97b535d languageName: node linkType: hard -"@inquirer/rawlist@npm:^5.2.8": - version: 5.2.8 - resolution: "@inquirer/rawlist@npm:5.2.8" +"@inquirer/search@npm:^3.0.13": + version: 3.0.13 + resolution: "@inquirer/search@npm:3.0.13" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/figures": "npm:^1.0.11" + "@inquirer/type": "npm:^3.0.6" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/ac975f95aecbf66c96f1814ab9d4601cb059ebc04b9371a3335dc71dc5c05141cf7c99502219785b187f3e53d86e65f823c5973e5c1b5d3b871d8e019d96ec3d + checksum: 10/2b8a94c5d83e4eced093caa680cb6561037d047702b91f77adc1ab56189d0c78974de0017946004b7acef9f8312772d7369ad227c0fbc59133ad3243981eff3d languageName: node linkType: hard -"@inquirer/search@npm:^4.1.8": - version: 4.1.8 - resolution: "@inquirer/search@npm:4.1.8" +"@inquirer/select@npm:^4.2.1": + version: 4.2.1 + resolution: "@inquirer/select@npm:4.2.1" dependencies: - "@inquirer/core": "npm:^11.1.9" - "@inquirer/figures": "npm:^2.0.5" - "@inquirer/type": "npm:^4.0.5" + "@inquirer/core": "npm:^10.1.11" + "@inquirer/figures": "npm:^1.0.11" + "@inquirer/type": "npm:^3.0.6" + ansi-escapes: "npm:^4.3.2" + yoctocolors-cjs: "npm:^2.1.2" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/a4ed90e0d71618b519c97e5180b93106c3d64ac9bfa3939d6a1a06f159d572fdd5a5f9be45225f8cd78913890965018d0108d8ad95c241ac5f38ccea2b1ba499 + checksum: 10/883ff2c359052efe9be021d5cf5133970c49f62ac07ba18fd949d71242a40608708f9a0651a1094c6e1dcbc914c40817646f57ac2b281b485fa331dd49232083 languageName: node linkType: hard -"@inquirer/select@npm:^5.1.4": - version: 5.1.4 - resolution: "@inquirer/select@npm:5.1.4" +"@inquirer/type@npm:^1.5.5": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" dependencies: - "@inquirer/ansi": "npm:^2.0.5" - "@inquirer/core": "npm:^11.1.9" - "@inquirer/figures": "npm:^2.0.5" - "@inquirer/type": "npm:^4.0.5" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10/e11893e979fccdd1b8d2d51858dd59f443c3062337f659961fbd11062854e542d6fdf6cdc71ff24846790a0251a16abc4bd38767eb44c26671e9a23a4e1da3ff + mute-stream: "npm:^1.0.0" + checksum: 10/bd3f3d7510785af4ad599e042e99e4be6380f52f79f3db140fe6fed0a605acf27b1a0a20fb5cc688eaf7b8aa0c36dacb1d89c7bba4586f38cbf58ba9f159e7b5 languageName: node linkType: hard -"@inquirer/type@npm:^4.0.5": - version: 4.0.5 - resolution: "@inquirer/type@npm:4.0.5" +"@inquirer/type@npm:^3.0.6": + version: 3.0.6 + resolution: "@inquirer/type@npm:3.0.6" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/83d15e11cc0586373070e8c262f69b1d1e4a6c72f58b3afb3d163479309f5a9bb584320eec2d85474506fb845a114e2c50010758fcf3af56c93293d579f76333 + checksum: 10/e3466c83934585cb180bc44ede36e9545e794c211f53ffa0390b1c70bb05fb79bacdc1173cdbe08c5ac72bd4186e34b4f10c1c4b94e0cba9abfb714742dd6201 languageName: node linkType: hard @@ -2791,16 +2355,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.12": - version: 0.3.13 - resolution: "@jridgewell/gen-mapping@npm:0.3.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/902f8261dcf450b4af7b93f9656918e02eec80a2169e155000cb2059f90113dd98f3ccf6efc6072cee1dd84cac48cade51da236972d942babc40e4c23da4d62a - languageName: node - linkType: hard - "@jridgewell/gen-mapping@npm:^0.3.5": version: 0.3.8 resolution: "@jridgewell/gen-mapping@npm:0.3.8" @@ -2812,16 +2366,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/remapping@npm:^2.3.5": - version: 2.3.5 - resolution: "@jridgewell/remapping@npm:2.3.5" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/c2bb01856e65b506d439455f28aceacf130d6c023d1d4e3b48705e88def3571753e1a887daa04b078b562316c92d26ce36408a60534bceca3f830aec88a339ad - languageName: node - linkType: hard - "@jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" @@ -2853,13 +2397,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -2870,26 +2407,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.31 - resolution: "@jridgewell/trace-mapping@npm:0.3.31" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 - languageName: node - linkType: hard - -"@jsonjoy.com/base64@npm:17.67.0": - version: 17.67.0 - resolution: "@jsonjoy.com/base64@npm:17.67.0" - peerDependencies: - tslib: 2 - checksum: 10/ae44b0c4c83ecc5c0ee1911706a665e18e89d64a2b705cc458d7f6fc3c3c7db0e621261e978d02b74ded6a9fe1aafc8e708eb8a133e794a92bb033c50a0c4ccd - languageName: node - linkType: hard - -"@jsonjoy.com/base64@npm:^1.1.1, @jsonjoy.com/base64@npm:^1.1.2": +"@jsonjoy.com/base64@npm:^1.1.1": version: 1.1.2 resolution: "@jsonjoy.com/base64@npm:1.1.2" peerDependencies: @@ -2898,145 +2416,6 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/buffers@npm:17.67.0, @jsonjoy.com/buffers@npm:^17.65.0": - version: 17.67.0 - resolution: "@jsonjoy.com/buffers@npm:17.67.0" - peerDependencies: - tslib: 2 - checksum: 10/6c8f6c4c73ec4ddab538a88be0bf72d8a934752755d43b0289fbe19ce9fa6123f082d1cd5ae179495e121a2f50267d26d36641f6dadedd8d5d2a2f980426e8ff - languageName: node - linkType: hard - -"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": - version: 1.2.1 - resolution: "@jsonjoy.com/buffers@npm:1.2.1" - peerDependencies: - tslib: 2 - checksum: 10/8ef4784d05c0fb4d0f27a1f78f5b0ae1f3b537d237f978d10be0b88f59a534ae44db2a4bde28eee0eb461ede31dc194aab5927ac001ed2b764629fa43ae9b60b - languageName: node - linkType: hard - -"@jsonjoy.com/codegen@npm:17.67.0": - version: 17.67.0 - resolution: "@jsonjoy.com/codegen@npm:17.67.0" - peerDependencies: - tslib: 2 - checksum: 10/e2462836c708999d045c4a15099f12e721089a3731f0ad33da210559a52ed763b8bddbec3c181857341984ef12ea355290609f37f0dc6f8de1545c028090adf5 - languageName: node - linkType: hard - -"@jsonjoy.com/codegen@npm:^1.0.0": - version: 1.0.0 - resolution: "@jsonjoy.com/codegen@npm:1.0.0" - peerDependencies: - tslib: 2 - checksum: 10/a0afb03d2af4fbc1377c547e507f5db99a25f515d8c4b6b2cef1ff28145ac59fff12b6e1f41f9734cb62ea5619e7f9be1acd0908305d6f4176898ee534ee9a64 - languageName: node - linkType: hard - -"@jsonjoy.com/fs-core@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-core@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - thingies: "npm:^2.5.0" - peerDependencies: - tslib: 2 - checksum: 10/6db8b3a7fb874229c7991bbdc094d752adbde7d774e5ef70df5a787130c7c8ed4ac2d34eaac079383c527269feaa91d1cb4f5c1504af995cca95070af769a0bd - languageName: node - linkType: hard - -"@jsonjoy.com/fs-fsa@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-fsa@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-core": "npm:4.57.2" - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - thingies: "npm:^2.5.0" - peerDependencies: - tslib: 2 - checksum: 10/0edf3b73d06a27e81f8a8e3b042022b9440c4794bb21d9957c15cd5c87f629e7e2f6695d464f82bb52d16e08fb3e682090ced5e712cc5bb05b41cbe99ce6e393 - languageName: node - linkType: hard - -"@jsonjoy.com/fs-node-builtins@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-node-builtins@npm:4.57.2" - peerDependencies: - tslib: 2 - checksum: 10/3284f0f0a989ad2bc0abc485748b2f3581648401c7d86be9b4541374f65050d384b61b5e44eff9b463d43fd1764bead1251783681105962ba5954b5e64b42480 - languageName: node - linkType: hard - -"@jsonjoy.com/fs-node-to-fsa@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-fsa": "npm:4.57.2" - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - peerDependencies: - tslib: 2 - checksum: 10/8d6e7447c640c02eb89c03e6a565af13b30607402a83ab462f0e16cced95d1cf0a09cc43fe297c379e51e905e0a6f7e14e65a65d19ece0756c3ae888e618e88c - languageName: node - linkType: hard - -"@jsonjoy.com/fs-node-utils@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-node-utils@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - peerDependencies: - tslib: 2 - checksum: 10/f63c7c8fd5a63a163a01bc70dac262419bcc1ae182186f249e038703b04a401e49eab9043514384555177e9385929b58a76cab945e8c7cdc6809efc2ea50bf31 - languageName: node - linkType: hard - -"@jsonjoy.com/fs-node@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-node@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-core": "npm:4.57.2" - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - "@jsonjoy.com/fs-print": "npm:4.57.2" - "@jsonjoy.com/fs-snapshot": "npm:4.57.2" - glob-to-regex.js: "npm:^1.0.0" - thingies: "npm:^2.5.0" - peerDependencies: - tslib: 2 - checksum: 10/2e7777874624035b5503a6d7cbefa82c06adcf3ad63140cd8ce83082b46dace5f8981f98121cb27c79f5755b3790623fbc9facf2b27b00aca28498d4d33df611 - languageName: node - linkType: hard - -"@jsonjoy.com/fs-print@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-print@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - tree-dump: "npm:^1.1.0" - peerDependencies: - tslib: 2 - checksum: 10/4931c8de684a655ab11ba2285016c35f3558f1f8f3444ac42fe7f5ea417556661b6f88d238c107f30c30b2d131eb2e0ecb1bb8626a7f6e0440996f42ea31dd7b - languageName: node - linkType: hard - -"@jsonjoy.com/fs-snapshot@npm:4.57.2": - version: 4.57.2 - resolution: "@jsonjoy.com/fs-snapshot@npm:4.57.2" - dependencies: - "@jsonjoy.com/buffers": "npm:^17.65.0" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - "@jsonjoy.com/json-pack": "npm:^17.65.0" - "@jsonjoy.com/util": "npm:^17.65.0" - peerDependencies: - tslib: 2 - checksum: 10/191b9d9a63f0ad30342da7ab37a45bbe84c935ae204e3780d69acf2fb12fdf07f7da8c3ade38255207de72fdb3b64a30e8dcc2ad93cfe424e77697d3cd419166 - languageName: node - linkType: hard - "@jsonjoy.com/json-pack@npm:^1.0.3": version: 1.1.1 resolution: "@jsonjoy.com/json-pack@npm:1.1.1" @@ -3051,77 +2430,6 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/json-pack@npm:^1.11.0": - version: 1.21.0 - resolution: "@jsonjoy.com/json-pack@npm:1.21.0" - dependencies: - "@jsonjoy.com/base64": "npm:^1.1.2" - "@jsonjoy.com/buffers": "npm:^1.2.0" - "@jsonjoy.com/codegen": "npm:^1.0.0" - "@jsonjoy.com/json-pointer": "npm:^1.0.2" - "@jsonjoy.com/util": "npm:^1.9.0" - hyperdyperid: "npm:^1.2.0" - thingies: "npm:^2.5.0" - tree-dump: "npm:^1.1.0" - peerDependencies: - tslib: 2 - checksum: 10/138b7eb8c96e6e435b0218c8f2eb5554e4eb49198a8718673a65e81da53b4617553ffa7124b51d6ea00fdfb868d6ff8b5ad6365e8336380ca7025f04d0412ee7 - languageName: node - linkType: hard - -"@jsonjoy.com/json-pack@npm:^17.65.0": - version: 17.67.0 - resolution: "@jsonjoy.com/json-pack@npm:17.67.0" - dependencies: - "@jsonjoy.com/base64": "npm:17.67.0" - "@jsonjoy.com/buffers": "npm:17.67.0" - "@jsonjoy.com/codegen": "npm:17.67.0" - "@jsonjoy.com/json-pointer": "npm:17.67.0" - "@jsonjoy.com/util": "npm:17.67.0" - hyperdyperid: "npm:^1.2.0" - thingies: "npm:^2.5.0" - tree-dump: "npm:^1.1.0" - peerDependencies: - tslib: 2 - checksum: 10/9ff4403862e49433fe607175e90af749d64902640d63919ba559e5748d1a3db60d7366cc3b84dcc4a57ad478540e5eecb22fed80766e293482a0ab8e583b1b0b - languageName: node - linkType: hard - -"@jsonjoy.com/json-pointer@npm:17.67.0": - version: 17.67.0 - resolution: "@jsonjoy.com/json-pointer@npm:17.67.0" - dependencies: - "@jsonjoy.com/util": "npm:17.67.0" - peerDependencies: - tslib: 2 - checksum: 10/5a27c6b5b1276d357cfc3e8a05112d6305ccd17bf672190f25dfac2f4108ced170e784451d64728f60f93305c0007e3f832ddd175b8a47f3eb652cbabcec31ad - languageName: node - linkType: hard - -"@jsonjoy.com/json-pointer@npm:^1.0.2": - version: 1.0.2 - resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" - dependencies: - "@jsonjoy.com/codegen": "npm:^1.0.0" - "@jsonjoy.com/util": "npm:^1.9.0" - peerDependencies: - tslib: 2 - checksum: 10/f22baeb3abc8ace2d8902d06ec297343431d4486dcf399aaaffd26ace7e62e194fe0efb4b7880e45b3b7939224ee838d3213448ef654fc8a61c91a76fe994d94 - languageName: node - linkType: hard - -"@jsonjoy.com/util@npm:17.67.0, @jsonjoy.com/util@npm:^17.65.0": - version: 17.67.0 - resolution: "@jsonjoy.com/util@npm:17.67.0" - dependencies: - "@jsonjoy.com/buffers": "npm:17.67.0" - "@jsonjoy.com/codegen": "npm:17.67.0" - peerDependencies: - tslib: 2 - checksum: 10/b0facf65c3190d6ed1ada7e5b7679d80fa5da73bfbd02f2bb2f3af1c28c0d854b6ee2350824313b7ba82c0e5191da94903b4af61255bc232dbb7feedd2f31e0c - languageName: node - linkType: hard - "@jsonjoy.com/util@npm:^1.1.2, @jsonjoy.com/util@npm:^1.3.0": version: 1.5.0 resolution: "@jsonjoy.com/util@npm:1.5.0" @@ -3131,18 +2439,6 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/util@npm:^1.9.0": - version: 1.9.0 - resolution: "@jsonjoy.com/util@npm:1.9.0" - dependencies: - "@jsonjoy.com/buffers": "npm:^1.0.0" - "@jsonjoy.com/codegen": "npm:^1.0.0" - peerDependencies: - tslib: 2 - checksum: 10/1a6e5301d725a7161b93ff707eb1a954bf4552a2fa96eee9a960d3ae3ed5f993d18b56dcff29e98036341a5968c5d1b2dfe21f76695390e7f0d89b81f24c85e0 - languageName: node - linkType: hard - "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.5 resolution: "@leichtgewicht/ip-codec@npm:2.0.5" @@ -3150,100 +2446,66 @@ __metadata: languageName: node linkType: hard -"@listr2/prompt-adapter-inquirer@npm:4.2.3": - version: 4.2.3 - resolution: "@listr2/prompt-adapter-inquirer@npm:4.2.3" +"@listr2/prompt-adapter-inquirer@npm:2.0.22": + version: 2.0.22 + resolution: "@listr2/prompt-adapter-inquirer@npm:2.0.22" dependencies: - "@inquirer/type": "npm:^4.0.5" + "@inquirer/type": "npm:^1.5.5" peerDependencies: - "@inquirer/prompts": ">= 3 < 9" - listr2: 10.2.1 - checksum: 10/3201ae5dbf8966a97c5c2a781bc61133afc60c790242cc2ad2d52d615943c770df1b7138977effab98545fbbac861b89a5ddafdfeb67b01558cd29a5aeb7c7de + "@inquirer/prompts": ">= 3 < 8" + checksum: 10/1cf48fd838eb563c979bfdceb48c8b9587297e9488c95ec413b6af58dace2ee1eb3243ad05eab775060772b14e830050bf1577920c2be83980490d524826e0b5 languageName: node linkType: hard -"@lmdb/lmdb-darwin-arm64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-darwin-arm64@npm:3.5.4" +"@lmdb/lmdb-darwin-arm64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-darwin-arm64@npm:3.3.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@lmdb/lmdb-darwin-x64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-darwin-x64@npm:3.5.4" +"@lmdb/lmdb-darwin-x64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-darwin-x64@npm:3.3.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@lmdb/lmdb-linux-arm64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-linux-arm64@npm:3.5.4" +"@lmdb/lmdb-linux-arm64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-linux-arm64@npm:3.3.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@lmdb/lmdb-linux-arm@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-linux-arm@npm:3.5.4" +"@lmdb/lmdb-linux-arm@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-linux-arm@npm:3.3.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@lmdb/lmdb-linux-x64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-linux-x64@npm:3.5.4" +"@lmdb/lmdb-linux-x64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-linux-x64@npm:3.3.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@lmdb/lmdb-win32-arm64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-win32-arm64@npm:3.5.4" +"@lmdb/lmdb-win32-arm64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-win32-arm64@npm:3.3.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@lmdb/lmdb-win32-x64@npm:3.5.4": - version: 3.5.4 - resolution: "@lmdb/lmdb-win32-x64@npm:3.5.4" +"@lmdb/lmdb-win32-x64@npm:3.3.0": + version: 3.3.0 + resolution: "@lmdb/lmdb-win32-x64@npm:3.3.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:1.29.0": - version: 1.29.0 - resolution: "@modelcontextprotocol/sdk@npm:1.29.0" - dependencies: - "@hono/node-server": "npm:^1.19.9" - ajv: "npm:^8.17.1" - ajv-formats: "npm:^3.0.1" - content-type: "npm:^1.0.5" - cors: "npm:^2.8.5" - cross-spawn: "npm:^7.0.5" - eventsource: "npm:^3.0.2" - eventsource-parser: "npm:^3.0.0" - express: "npm:^5.2.1" - express-rate-limit: "npm:^8.2.1" - hono: "npm:^4.11.4" - jose: "npm:^6.1.3" - json-schema-typed: "npm:^8.0.2" - pkce-challenge: "npm:^5.0.0" - raw-body: "npm:^3.0.0" - zod: "npm:^3.25 || ^4.0" - zod-to-json-schema: "npm:^3.25.1" - peerDependencies: - "@cfworker/json-schema": ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - "@cfworker/json-schema": - optional: true - zod: - optional: false - checksum: 10/ff551b97e06b661f95fec8fd34e112c446e69894a84a9979cdac369fb5de27f0a1a5c1f4e2a1f270cc60f93e54c28a8059a94ca51c3d528d2670ade874b244f9 - languageName: node - linkType: hard - "@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3": version: 3.0.3 resolution: "@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3" @@ -3286,146 +2548,138 @@ __metadata: languageName: node linkType: hard -"@napi-rs/nice-android-arm-eabi@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-android-arm-eabi@npm:1.1.1" +"@napi-rs/nice-android-arm-eabi@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-android-arm-eabi@npm:1.0.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@napi-rs/nice-android-arm64@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-android-arm64@npm:1.1.1" +"@napi-rs/nice-android-arm64@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-android-arm64@npm:1.0.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@napi-rs/nice-darwin-arm64@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-darwin-arm64@npm:1.1.1" +"@napi-rs/nice-darwin-arm64@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-darwin-arm64@npm:1.0.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@napi-rs/nice-darwin-x64@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-darwin-x64@npm:1.1.1" +"@napi-rs/nice-darwin-x64@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-darwin-x64@npm:1.0.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@napi-rs/nice-freebsd-x64@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-freebsd-x64@npm:1.1.1" +"@napi-rs/nice-freebsd-x64@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-freebsd-x64@npm:1.0.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1" +"@napi-rs/nice-linux-arm-gnueabihf@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-arm-gnueabihf@npm:1.0.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@napi-rs/nice-linux-arm64-gnu@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-arm64-gnu@npm:1.1.1" +"@napi-rs/nice-linux-arm64-gnu@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-arm64-gnu@npm:1.0.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@napi-rs/nice-linux-arm64-musl@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-arm64-musl@npm:1.1.1" +"@napi-rs/nice-linux-arm64-musl@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-arm64-musl@npm:1.0.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1" +"@napi-rs/nice-linux-ppc64-gnu@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-ppc64-gnu@npm:1.0.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1" +"@napi-rs/nice-linux-riscv64-gnu@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-riscv64-gnu@npm:1.0.1" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@napi-rs/nice-linux-s390x-gnu@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-s390x-gnu@npm:1.1.1" +"@napi-rs/nice-linux-s390x-gnu@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-s390x-gnu@npm:1.0.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@napi-rs/nice-linux-x64-gnu@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-x64-gnu@npm:1.1.1" +"@napi-rs/nice-linux-x64-gnu@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-x64-gnu@npm:1.0.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@napi-rs/nice-linux-x64-musl@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-linux-x64-musl@npm:1.1.1" +"@napi-rs/nice-linux-x64-musl@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-linux-x64-musl@npm:1.0.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@napi-rs/nice-openharmony-arm64@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-openharmony-arm64@npm:1.1.1" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@napi-rs/nice-win32-arm64-msvc@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-win32-arm64-msvc@npm:1.1.1" +"@napi-rs/nice-win32-arm64-msvc@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-win32-arm64-msvc@npm:1.0.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@napi-rs/nice-win32-ia32-msvc@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-win32-ia32-msvc@npm:1.1.1" +"@napi-rs/nice-win32-ia32-msvc@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-win32-ia32-msvc@npm:1.0.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@napi-rs/nice-win32-x64-msvc@npm:1.1.1": - version: 1.1.1 - resolution: "@napi-rs/nice-win32-x64-msvc@npm:1.1.1" +"@napi-rs/nice-win32-x64-msvc@npm:1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice-win32-x64-msvc@npm:1.0.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@napi-rs/nice@npm:^1.0.4": - version: 1.1.1 - resolution: "@napi-rs/nice@npm:1.1.1" - dependencies: - "@napi-rs/nice-android-arm-eabi": "npm:1.1.1" - "@napi-rs/nice-android-arm64": "npm:1.1.1" - "@napi-rs/nice-darwin-arm64": "npm:1.1.1" - "@napi-rs/nice-darwin-x64": "npm:1.1.1" - "@napi-rs/nice-freebsd-x64": "npm:1.1.1" - "@napi-rs/nice-linux-arm-gnueabihf": "npm:1.1.1" - "@napi-rs/nice-linux-arm64-gnu": "npm:1.1.1" - "@napi-rs/nice-linux-arm64-musl": "npm:1.1.1" - "@napi-rs/nice-linux-ppc64-gnu": "npm:1.1.1" - "@napi-rs/nice-linux-riscv64-gnu": "npm:1.1.1" - "@napi-rs/nice-linux-s390x-gnu": "npm:1.1.1" - "@napi-rs/nice-linux-x64-gnu": "npm:1.1.1" - "@napi-rs/nice-linux-x64-musl": "npm:1.1.1" - "@napi-rs/nice-openharmony-arm64": "npm:1.1.1" - "@napi-rs/nice-win32-arm64-msvc": "npm:1.1.1" - "@napi-rs/nice-win32-ia32-msvc": "npm:1.1.1" - "@napi-rs/nice-win32-x64-msvc": "npm:1.1.1" +"@napi-rs/nice@npm:^1.0.1": + version: 1.0.1 + resolution: "@napi-rs/nice@npm:1.0.1" + dependencies: + "@napi-rs/nice-android-arm-eabi": "npm:1.0.1" + "@napi-rs/nice-android-arm64": "npm:1.0.1" + "@napi-rs/nice-darwin-arm64": "npm:1.0.1" + "@napi-rs/nice-darwin-x64": "npm:1.0.1" + "@napi-rs/nice-freebsd-x64": "npm:1.0.1" + "@napi-rs/nice-linux-arm-gnueabihf": "npm:1.0.1" + "@napi-rs/nice-linux-arm64-gnu": "npm:1.0.1" + "@napi-rs/nice-linux-arm64-musl": "npm:1.0.1" + "@napi-rs/nice-linux-ppc64-gnu": "npm:1.0.1" + "@napi-rs/nice-linux-riscv64-gnu": "npm:1.0.1" + "@napi-rs/nice-linux-s390x-gnu": "npm:1.0.1" + "@napi-rs/nice-linux-x64-gnu": "npm:1.0.1" + "@napi-rs/nice-linux-x64-musl": "npm:1.0.1" + "@napi-rs/nice-win32-arm64-msvc": "npm:1.0.1" + "@napi-rs/nice-win32-ia32-msvc": "npm:1.0.1" + "@napi-rs/nice-win32-x64-msvc": "npm:1.0.1" dependenciesMeta: "@napi-rs/nice-android-arm-eabi": optional: true @@ -3453,33 +2707,51 @@ __metadata: optional: true "@napi-rs/nice-linux-x64-musl": optional: true - "@napi-rs/nice-openharmony-arm64": - optional: true "@napi-rs/nice-win32-arm64-msvc": optional: true "@napi-rs/nice-win32-ia32-msvc": optional: true "@napi-rs/nice-win32-x64-msvc": optional: true - checksum: 10/3f197c9536d0294f732a2acbe05a6d2fddc2794873b5b73edd395f56e3aed90b46c053001af80ea006d4d276cbb4e4196f8dbee0c214163b8e4b787e570a37e1 + checksum: 10/ae265aa365b325830115c1cda49b05ea05e6f1163944a1485c0643c9552380cd32a2aaf12b326f353538ca6244222963eb2e9767a4713c9432eadecd027f90ea languageName: node linkType: hard -"@ngtools/webpack@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@ngtools/webpack@npm:22.0.0-next.6" +"@ngtools/webpack@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@ngtools/webpack@npm:20.0.0-rc.3" peerDependencies: - "@angular/compiler-cli": ^22.0.0-next.0 - typescript: ">=6.0 <6.1" + "@angular/compiler-cli": ^20.0.0 || ^20.0.0-next.0 + typescript: ">=5.8 <5.9" webpack: ^5.54.0 - checksum: 10/6fe410441637ed30e082b26e7ce4fa2a71a4d7f38cb4b5da48388de821d5ae11778e8819729f20606b2e120eef4379ee956ebcdca104ed884d11addf15845be7 + checksum: 10/9318e8287fd5ae43e54eb20cb8ea338a1f0a44fda6f7d59920a892c319421aabe3bf6e7a4452abada20a43bfba44af70c8871e1ba9ebca822f10e453ab5ec28b languageName: node linkType: hard -"@noble/hashes@npm:1.4.0": - version: 1.4.0 - resolution: "@noble/hashes@npm:1.4.0" - checksum: 10/e156e65794c473794c52fa9d06baf1eb20903d0d96719530f523cc4450f6c721a957c544796e6efd0197b2296e7cd70efeb312f861465e17940a3e3c7e0febc6 +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 languageName: node linkType: hard @@ -3496,19 +2768,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/agent@npm:4.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^11.2.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10/1a81573becc60515031accc696e6405e9b894e65c12b98ef4aeee03b5617c41948633159dbf6caf5dde5b47367eeb749bdc7b7dfb21960930a9060a935c6f636 - languageName: node - linkType: hard - "@npmcli/fs@npm:^4.0.0": version: 4.0.0 resolution: "@npmcli/fs@npm:4.0.0" @@ -3518,91 +2777,83 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^5.0.0": - version: 5.0.0 - resolution: "@npmcli/fs@npm:5.0.0" +"@npmcli/git@npm:^6.0.0": + version: 6.0.3 + resolution: "@npmcli/git@npm:6.0.3" dependencies: + "@npmcli/promise-spawn": "npm:^8.0.0" + ini: "npm:^5.0.0" + lru-cache: "npm:^10.0.1" + npm-pick-manifest: "npm:^10.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" semver: "npm:^7.3.5" - checksum: 10/4935c7719d17830d0f9fa46c50be17b2a3c945cec61760f6d0909bce47677c42e1810ca673305890f9e84f008ec4d8e841182f371e42100a8159d15f22249208 - languageName: node - linkType: hard - -"@npmcli/git@npm:^7.0.0": - version: 7.0.2 - resolution: "@npmcli/git@npm:7.0.2" - dependencies: - "@gar/promise-retry": "npm:^1.0.0" - "@npmcli/promise-spawn": "npm:^9.0.0" - ini: "npm:^6.0.0" - lru-cache: "npm:^11.2.1" - npm-pick-manifest: "npm:^11.0.1" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - which: "npm:^6.0.0" - checksum: 10/bb90a3d0ba2a2bea8bb9c44361b87fa9f2cc12a629852031af9e523bdc292e4cd79712cdb384814e55785d46b684e5c5912ee637ecafa209fc3ff3bad243ab90 + which: "npm:^5.0.0" + checksum: 10/aef520bb32c13012568dfb9f4ae90cb214d7fc45736012cd9415f0ac80f76ddf6a582f8d524adf3f4bab50e5c9d35b5370589bc630377cbfd06a618504faa689 languageName: node linkType: hard -"@npmcli/installed-package-contents@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/installed-package-contents@npm:4.0.0" +"@npmcli/installed-package-contents@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/installed-package-contents@npm:3.0.0" dependencies: - npm-bundled: "npm:^5.0.0" - npm-normalize-package-bin: "npm:^5.0.0" + npm-bundled: "npm:^4.0.0" + npm-normalize-package-bin: "npm:^4.0.0" bin: installed-package-contents: bin/index.js - checksum: 10/a3f1676ebef398639f97462c78eea3cee69b41fda63dfc1d7c83f88c75379728d78a622d93eec07a2f94456011480bcd43a73949f21d52775d9d1f8c7633abe1 + checksum: 10/00fc2f0bdb63c510219a2d47ac0eb3cfaed9208efa4e1fe701eb976b91e6d08a533705a0629cbd3eb66a2b1a93abe8176b80723b9968ce874adbc299035f2fa5 languageName: node linkType: hard -"@npmcli/node-gyp@npm:^5.0.0": - version: 5.0.0 - resolution: "@npmcli/node-gyp@npm:5.0.0" - checksum: 10/31488b0a0a6293efc4ab1bd87ba483d1000f8720c5f068d4c28cf49e39a045cd122960ba2a166a376fc9767f457f6124d99ec673ebcb19015cd29835bb038e46 +"@npmcli/node-gyp@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/node-gyp@npm:4.0.0" + checksum: 10/edfbdc66dcb35b769d27f1d34b6149957a15fdf56d6f9dd01120720f2d56dbeb825e4b2fad0eebb36855f8a741a5128683c69c2d024412d799df843c32af3d5d languageName: node linkType: hard -"@npmcli/package-json@npm:^7.0.0": - version: 7.0.5 - resolution: "@npmcli/package-json@npm:7.0.5" +"@npmcli/package-json@npm:^6.0.0": + version: 6.1.1 + resolution: "@npmcli/package-json@npm:6.1.1" dependencies: - "@npmcli/git": "npm:^7.0.0" - glob: "npm:^13.0.0" - hosted-git-info: "npm:^9.0.0" - json-parse-even-better-errors: "npm:^5.0.0" - proc-log: "npm:^6.0.0" + "@npmcli/git": "npm:^6.0.0" + glob: "npm:^10.2.2" + hosted-git-info: "npm:^8.0.0" + json-parse-even-better-errors: "npm:^4.0.0" + proc-log: "npm:^5.0.0" semver: "npm:^7.5.3" - spdx-expression-parse: "npm:^4.0.0" - checksum: 10/d07a5bb98f59675afa51c0a8ba1f32d7a459da36c14e2ad2b2dd6e312c99684fd3a76f5cc497376af588fc98a2be7d05651e5a58c8a282f12dcfed44c44338fa + validate-npm-package-license: "npm:^3.0.4" + checksum: 10/dd79cda6d01b71a3affb66fed38510b0e06f2d727077e9b3bd49842d3c6ac19abfd0710f23d82a20289f8f98cf58d906ab2144343d45850ff49fa5741afc1f61 languageName: node linkType: hard -"@npmcli/promise-spawn@npm:^9.0.0": - version: 9.0.1 - resolution: "@npmcli/promise-spawn@npm:9.0.1" +"@npmcli/promise-spawn@npm:^8.0.0": + version: 8.0.2 + resolution: "@npmcli/promise-spawn@npm:8.0.2" dependencies: - which: "npm:^6.0.0" - checksum: 10/93f539f12813dacf0084c5f444982d44c67f2016f417f2e937afb81c3fd228cf330abeabdffc95ca3e8315a4a9b9e732be7e7870c926d7dfc6c458549fcd11ea + which: "npm:^5.0.0" + checksum: 10/e23b62cea2f09184830a89d13bef09fd7363db9edb77f0169fada2724be797db83770488f4229479ab2639ec306afd60c4c96b016b387ece68b0d726b875f5c9 languageName: node linkType: hard -"@npmcli/redact@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/redact@npm:4.0.0" - checksum: 10/5d52df2b5267f4369c97a2b2f7c427e3d7aa4b6a83e7a1b522e196f6e9d50024c620bd0cb2052067c74d1aaa0c330d9bc04e1d335bfb46180e705bb33423e74c +"@npmcli/redact@npm:^3.0.0": + version: 3.1.1 + resolution: "@npmcli/redact@npm:3.1.1" + checksum: 10/8a2f6cb6e0a42474731bfd0888dbff290efece779636b4d93a520339d3a8d6f3c5460ce03d871fcbc1c77e9864b4558340c5792784f1aac0cc493124dbac790c languageName: node linkType: hard -"@npmcli/run-script@npm:^10.0.0": - version: 10.0.4 - resolution: "@npmcli/run-script@npm:10.0.4" +"@npmcli/run-script@npm:^9.0.0": + version: 9.0.2 + resolution: "@npmcli/run-script@npm:9.0.2" dependencies: - "@npmcli/node-gyp": "npm:^5.0.0" - "@npmcli/package-json": "npm:^7.0.0" - "@npmcli/promise-spawn": "npm:^9.0.0" - node-gyp: "npm:^12.1.0" - proc-log: "npm:^6.0.0" - checksum: 10/dd5f92aa6c50761c125eb836432497edfe57a32ddde175218167515bc8f54ba82b7fa8b89b8f73eda69c3a88d1ffe97e14080b316aefdddc264c450e24c32c8b + "@npmcli/node-gyp": "npm:^4.0.0" + "@npmcli/package-json": "npm:^6.0.0" + "@npmcli/promise-spawn": "npm:^8.0.0" + node-gyp: "npm:^11.0.0" + proc-log: "npm:^5.0.0" + which: "npm:^5.0.0" + checksum: 10/b591e2deef80f77374110fa99e80b13e7e22144d35b77204d1b0d5ca0270ce5552be3e34343bf23d78ef737b6ad8a017642deeac8c7a3263c97e9236e087cf3c languageName: node linkType: hard @@ -3750,151 +3001,6 @@ __metadata: languageName: node linkType: hard -"@peculiar/asn1-cms@npm:^2.6.0, @peculiar/asn1-cms@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-cms@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - "@peculiar/asn1-x509-attr": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/e431f6229b98c63a929538d266488e8c2dddc895936117da8f9ec775558e08c20ded6a4adcca4bb88bfea282e7204d4f6bba7a46da2cced162c174e1e6964f36 - languageName: node - linkType: hard - -"@peculiar/asn1-csr@npm:^2.6.0": - version: 2.6.1 - resolution: "@peculiar/asn1-csr@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/4ac2f1c3a2cb392fcdd5aa602140abe90f849af0a9e8296aab9aaf1712ee2e0c4f5fa86b0fe83975e771b0aba91fc848670f9c2008ea1e850c849fae6e181179 - languageName: node - linkType: hard - -"@peculiar/asn1-ecc@npm:^2.6.0": - version: 2.6.1 - resolution: "@peculiar/asn1-ecc@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/baa646c1c86283d5876230b1cfbd80cf42f97b3bb8d8b23cd5830f6f8d6466e6a06887c6838f3c4c61c87df9ffd2abe905f555472e8e70d722ce964a8074d838 - languageName: node - linkType: hard - -"@peculiar/asn1-pfx@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-pfx@npm:2.6.1" - dependencies: - "@peculiar/asn1-cms": "npm:^2.6.1" - "@peculiar/asn1-pkcs8": "npm:^2.6.1" - "@peculiar/asn1-rsa": "npm:^2.6.1" - "@peculiar/asn1-schema": "npm:^2.6.0" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/50adc7db96928d98b85a1a2e6765ba1d4ec708f937b8172ea6a22e3b92137ea36d656aded64b3be661db39f924102c5a80da54ee647e2441af3bc19c55a183ef - languageName: node - linkType: hard - -"@peculiar/asn1-pkcs8@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-pkcs8@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/99c4326da30e7ef17bb8e92d8a9525b78c101e4d743493000e220f3da6bbc4755371f1dbcc2a36951fb15769c2efead20d90a08918fd268c21bebcac26e71053 - languageName: node - linkType: hard - -"@peculiar/asn1-pkcs9@npm:^2.6.0": - version: 2.6.1 - resolution: "@peculiar/asn1-pkcs9@npm:2.6.1" - dependencies: - "@peculiar/asn1-cms": "npm:^2.6.1" - "@peculiar/asn1-pfx": "npm:^2.6.1" - "@peculiar/asn1-pkcs8": "npm:^2.6.1" - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - "@peculiar/asn1-x509-attr": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/61759a50d6adf108a0376735b2e76cdfc9c41db39a7abed23ca332f7699d831aa6324534aa38153018a31e6ee5e8fef85534c92b68067f6afcb90787e953c449 - languageName: node - linkType: hard - -"@peculiar/asn1-rsa@npm:^2.6.0, @peculiar/asn1-rsa@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-rsa@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/e91efe57017feac71c69ee5950e9c323b45aaf10baa32153fe88f237948f9d906ba04c645d085c4293c90440cad95392a91b3760251cd0ebc8e4c1a383fc331a - languageName: node - linkType: hard - -"@peculiar/asn1-schema@npm:^2.6.0": - version: 2.6.0 - resolution: "@peculiar/asn1-schema@npm:2.6.0" - dependencies: - asn1js: "npm:^3.0.6" - pvtsutils: "npm:^1.3.6" - tslib: "npm:^2.8.1" - checksum: 10/af9b1094d0e020f0fd828777488578322d62a41f597ead7d80939dafcfe35b672fcb0ec7460ef66b2a155f9614d4340a98896d417a830aff1685cb4c21d5bbe4 - languageName: node - linkType: hard - -"@peculiar/asn1-x509-attr@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-x509-attr@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.1" - asn1js: "npm:^3.0.6" - tslib: "npm:^2.8.1" - checksum: 10/86f7d5495459dee81daadd830ebb7d26ec15a98f6479c88b90a915ac9f28105b0d5003ba0c382b4aa8f7fa42e399f7cc37e4fe73c26cbaacd47e63a50b132e25 - languageName: node - linkType: hard - -"@peculiar/asn1-x509@npm:^2.6.0, @peculiar/asn1-x509@npm:^2.6.1": - version: 2.6.1 - resolution: "@peculiar/asn1-x509@npm:2.6.1" - dependencies: - "@peculiar/asn1-schema": "npm:^2.6.0" - asn1js: "npm:^3.0.6" - pvtsutils: "npm:^1.3.6" - tslib: "npm:^2.8.1" - checksum: 10/e3187ad04d397cdd6a946895a51202b67f57992dfef55e40acc7e7ea325e2854267ed2581c4b1ea729d7147e9e8e6f34af77f1ffb48e3e8b25b2216b213b4641 - languageName: node - linkType: hard - -"@peculiar/x509@npm:^1.14.2": - version: 1.14.3 - resolution: "@peculiar/x509@npm:1.14.3" - dependencies: - "@peculiar/asn1-cms": "npm:^2.6.0" - "@peculiar/asn1-csr": "npm:^2.6.0" - "@peculiar/asn1-ecc": "npm:^2.6.0" - "@peculiar/asn1-pkcs9": "npm:^2.6.0" - "@peculiar/asn1-rsa": "npm:^2.6.0" - "@peculiar/asn1-schema": "npm:^2.6.0" - "@peculiar/asn1-x509": "npm:^2.6.0" - pvtsutils: "npm:^1.3.6" - reflect-metadata: "npm:^0.2.2" - tslib: "npm:^2.8.1" - tsyringe: "npm:^4.10.0" - checksum: 10/d37c56fa5f2c644141948d85010e14f0e4963089e3b0b81edd0bfe85bdfea0eb3f38ab6ff20d322db2bd6977117824cc498a77b2d35af111983b4d58b5e2ccd1 - languageName: node - linkType: hard - "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -3902,247 +3008,212 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.2" +"@rollup/rollup-android-arm-eabi@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.40.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-android-arm64@npm:4.60.2" +"@rollup/rollup-android-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-android-arm64@npm:4.40.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.60.2" +"@rollup/rollup-darwin-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.40.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.60.2" +"@rollup/rollup-darwin-x64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.40.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.2" +"@rollup/rollup-freebsd-arm64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.40.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.60.2" +"@rollup/rollup-freebsd-x64@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.40.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.40.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.40.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.2" +"@rollup/rollup-linux-arm64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.40.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.2" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-loong64-musl@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.2" - conditions: os=linux & cpu=loong64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.2" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-musl@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.2" - conditions: os=linux & cpu=ppc64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.40.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.40.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.40.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.2" +"@rollup/rollup-linux-x64-gnu@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.40.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.2" +"@rollup/rollup-linux-x64-musl@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.40.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openbsd-x64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-openbsd-x64@npm:4.60.2" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-openharmony-arm64@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.60.2" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.60.2" +"@rollup/rollup-win32-arm64-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.40.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.60.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.40.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.60.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.60.2": - version: 4.60.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.60.2" +"@rollup/rollup-win32-x64-msvc@npm:4.40.2": + version: 4.40.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.40.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@schematics/angular@npm:22.0.0-next.6": - version: 22.0.0-next.6 - resolution: "@schematics/angular@npm:22.0.0-next.6" +"@schematics/angular@npm:20.0.0-rc.3": + version: 20.0.0-rc.3 + resolution: "@schematics/angular@npm:20.0.0-rc.3" dependencies: - "@angular-devkit/core": "npm:22.0.0-next.6" - "@angular-devkit/schematics": "npm:22.0.0-next.6" + "@angular-devkit/core": "npm:20.0.0-rc.3" + "@angular-devkit/schematics": "npm:20.0.0-rc.3" jsonc-parser: "npm:3.3.1" - checksum: 10/fafcb4bf817956588508267f89b6d9c406626ef64508ec2919d6bced1958b9c3c36df711db91fd7c9f37971500fdf67e0002a2a5ca3ac13b75deb963cf5f0a6e + checksum: 10/087c45755a4e3d44c4c08e2016b9c7ee9afb08a313dc0e8fda560f507d24e6d679eca8224846759dfab5db6fe26531af320e917528d334d80356acd69a09e5b7 languageName: node linkType: hard -"@sigstore/bundle@npm:^4.0.0": - version: 4.0.0 - resolution: "@sigstore/bundle@npm:4.0.0" +"@sigstore/bundle@npm:^3.1.0": + version: 3.1.0 + resolution: "@sigstore/bundle@npm:3.1.0" dependencies: - "@sigstore/protobuf-specs": "npm:^0.5.0" - checksum: 10/09ef32284783cdcdcc7ecd16711f1d1be6b6fc6abe22bf7434071a6d3aa3512d15f68a4cc481513569a55a001c5bd112edfccbea7b3c16b5aa1557f73773f504 + "@sigstore/protobuf-specs": "npm:^0.4.0" + checksum: 10/21b246ec63462e8508a8d001ca5d7937f63b6e15d5f2947ee2726d1e4674fb3f7640faa47b165bfea1d5b09df93fbdf10d1556427bba7e005e7f3a65b87f89b2 languageName: node linkType: hard -"@sigstore/core@npm:^3.1.0, @sigstore/core@npm:^3.2.0": - version: 3.2.0 - resolution: "@sigstore/core@npm:3.2.0" - checksum: 10/2425d20297d57a5f5a62f0e6c2f4280818015ea00b3defebdac63f13c7d01db988602c316c16e374ba091c3649dd9a22ae8c9ba3ac165f736b0503164c5da5f5 +"@sigstore/core@npm:^2.0.0": + version: 2.0.0 + resolution: "@sigstore/core@npm:2.0.0" + checksum: 10/ec1deae9430eeff580ad0f4ef2328b4eb7252db04587474fe9423d97736134ad79ee83aa2dfbc1fccfb18420c249e26e6e72e7176b592d7013eae5379dcb124d languageName: node linkType: hard -"@sigstore/protobuf-specs@npm:^0.5.0": - version: 0.5.1 - resolution: "@sigstore/protobuf-specs@npm:0.5.1" - checksum: 10/2ca6b044ab70e6aa85cc0b67f2d67724cf8b9efc49d6f7fd65993ee9b9aea02a5e8e7a73cc2a75e1968f2aa231f79d28e4bb7e88c1f98274405214e4cb1568b2 +"@sigstore/protobuf-specs@npm:^0.4.0": + version: 0.4.0 + resolution: "@sigstore/protobuf-specs@npm:0.4.0" + checksum: 10/b267b24c8aa66579de887192d7e7c5feae6cbe75c1911b217cae91dad9e9a3b230556c611775a1845e8f8f1e378b0821ca6beb3877a819e64dd00b2f120a93f4 languageName: node linkType: hard -"@sigstore/sign@npm:^4.1.0": - version: 4.1.1 - resolution: "@sigstore/sign@npm:4.1.1" +"@sigstore/sign@npm:^3.1.0": + version: 3.1.0 + resolution: "@sigstore/sign@npm:3.1.0" dependencies: - "@gar/promise-retry": "npm:^1.0.2" - "@sigstore/bundle": "npm:^4.0.0" - "@sigstore/core": "npm:^3.2.0" - "@sigstore/protobuf-specs": "npm:^0.5.0" - make-fetch-happen: "npm:^15.0.4" - proc-log: "npm:^6.1.0" - checksum: 10/c9424813ed83ae26111dd3a190dbfd776901cfc245ebb9aa68e133a7ffcbf8fc053f01d999a451e44805a291921ba4d2dfe80e3fd41b20cd5becd26aae5f5e7c + "@sigstore/bundle": "npm:^3.1.0" + "@sigstore/core": "npm:^2.0.0" + "@sigstore/protobuf-specs": "npm:^0.4.0" + make-fetch-happen: "npm:^14.0.2" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + checksum: 10/e0ce0aa52b572eefa06a8260a7329f349c56217f2bbb6f167259c6e02e148987073e0dddc5e3c40ea4aafc89b8b0176e2617fb16f9c8c50cf0c1437b6c90fca4 languageName: node linkType: hard -"@sigstore/tuf@npm:^4.0.1": - version: 4.0.2 - resolution: "@sigstore/tuf@npm:4.0.2" +"@sigstore/tuf@npm:^3.1.0": + version: 3.1.0 + resolution: "@sigstore/tuf@npm:3.1.0" dependencies: - "@sigstore/protobuf-specs": "npm:^0.5.0" - tuf-js: "npm:^4.1.0" - checksum: 10/14882b8e71be4185ec417744b97a47392a50da00aafd4207a46bb74b40aa019ebf22d928052fd2d31a8da0da1efe7ebebac5a70898b31a74239a1ada997be754 + "@sigstore/protobuf-specs": "npm:^0.4.0" + tuf-js: "npm:^3.0.1" + checksum: 10/7040aaa8b05ab2ff62fec078ccb2ebfe8d96b862ad11dc4daebb707e11b72e424f54c55b6878b6a5b6b551afd1209078dc4140dda0f93c5b3afac7cc5fb9bb16 languageName: node linkType: hard -"@sigstore/verify@npm:^3.1.0": - version: 3.1.0 - resolution: "@sigstore/verify@npm:3.1.0" +"@sigstore/verify@npm:^2.1.0": + version: 2.1.0 + resolution: "@sigstore/verify@npm:2.1.0" dependencies: - "@sigstore/bundle": "npm:^4.0.0" - "@sigstore/core": "npm:^3.1.0" - "@sigstore/protobuf-specs": "npm:^0.5.0" - checksum: 10/c85713cc326236ef39608e4b061c1192306fd3edd7a1334237d5d53dbb132f04e3f9d3cfd4bb2d521bf0c95a9f98945a748c97ecb06e5f36cfd09488a0d3d73f + "@sigstore/bundle": "npm:^3.1.0" + "@sigstore/core": "npm:^2.0.0" + "@sigstore/protobuf-specs": "npm:^0.4.0" + checksum: 10/bb0a8472c80d27f0647106f18fe71112262bc13f21384c224c62bfd69e0672e0dd635537e7df566018d37f6604c437f162bcbdfe0a9d427d77541da7f36b51eb languageName: node linkType: hard @@ -4153,13 +3224,6 @@ __metadata: languageName: node linkType: hard -"@standard-schema/spec@npm:^1.0.0": - version: 1.1.0 - resolution: "@standard-schema/spec@npm:1.1.0" - checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 - languageName: node - linkType: hard - "@tufjs/canonical-json@npm:2.0.0": version: 2.0.0 resolution: "@tufjs/canonical-json@npm:2.0.0" @@ -4167,13 +3231,13 @@ __metadata: languageName: node linkType: hard -"@tufjs/models@npm:4.1.0": - version: 4.1.0 - resolution: "@tufjs/models@npm:4.1.0" +"@tufjs/models@npm:3.0.1": + version: 3.0.1 + resolution: "@tufjs/models@npm:3.0.1" dependencies: "@tufjs/canonical-json": "npm:2.0.0" - minimatch: "npm:^10.1.1" - checksum: 10/144d58b634ff96bba8f3cc2577868a0c5dd5bb4515c191edc2a9971245fe3694603b56f0515fd4f7b2f1fb73642d4a36b59b0094ba773fe1c14550915bc9af43 + minimatch: "npm:^9.0.5" + checksum: 10/00636238b2e3ce557e7b5f6884594ee2379fd5a9588401fd6c8be2e2867fcaf836e226c6be81d87006701746037847e13bbc263c0ed0f38b4f28b1108a4b1d81 languageName: node linkType: hard @@ -4244,17 +3308,17 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*": +"@types/estree@npm:*, @types/estree@npm:^1.0.6": version: 1.0.6 resolution: "@types/estree@npm:1.0.6" checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d languageName: node linkType: hard -"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 +"@types/estree@npm:1.0.7": + version: 1.0.7 + resolution: "@types/estree@npm:1.0.7" + checksum: 10/419c845ece767ad4b21171e6e5b63dabb2eb46b9c0d97361edcd9cabbf6a95fcadb91d89b5fa098d1336fa0b8fceaea82fca97a2ef3971f5c86e53031e157b21 languageName: node linkType: hard @@ -4294,15 +3358,15 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:^4.17.25": - version: 4.17.25 - resolution: "@types/express@npm:4.17.25" +"@types/express@npm:^4.17.21": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" - "@types/serve-static": "npm:^1" - checksum: 10/c309fdb79fb8569b5d8d8f11268d0160b271f8b38f0a82c20a0733e526baf033eb7a921cd51d54fe4333c616de9e31caf7d4f3ef73baaf212d61f23f460b0369 + "@types/serve-static": "npm:*" + checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 languageName: node linkType: hard @@ -4343,6 +3407,15 @@ __metadata: languageName: node linkType: hard +"@types/node-forge@npm:^1.3.0": + version: 1.3.11 + resolution: "@types/node-forge@npm:1.3.11" + dependencies: + "@types/node": "npm:*" + checksum: 10/670c9b377c48189186ec415e3c8ed371f141ecc1a79ab71b213b20816adeffecba44dae4f8406cc0d09e6349a4db14eb8c5893f643d8e00fa19fc035cf49dee0 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=10.0.0": version: 22.13.9 resolution: "@types/node@npm:22.13.9" @@ -4392,16 +3465,6 @@ __metadata: languageName: node linkType: hard -"@types/send@npm:<1": - version: 0.17.6 - resolution: "@types/send@npm:0.17.6" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10/4948ab32ab84a81a0073f8243dd48ee766bc80608d5391060360afd1249f83c08a7476f142669ac0b0b8831c89d909a88bcb392d1b39ee48b276a91b50f3d8d1 - languageName: node - linkType: hard - "@types/serve-index@npm:^1.9.4": version: 1.9.4 resolution: "@types/serve-index@npm:1.9.4" @@ -4422,17 +3485,6 @@ __metadata: languageName: node linkType: hard -"@types/serve-static@npm:^1": - version: 1.15.10 - resolution: "@types/serve-static@npm:1.15.10" - dependencies: - "@types/http-errors": "npm:*" - "@types/node": "npm:*" - "@types/send": "npm:<1" - checksum: 10/d9be72487540b9598e7d77260d533f241eb2e5db5181bb885ef2d6bc4592dad1c9e8c0e27f465d59478b2faf90edd2d535e834f20fbd9dd3c0928d43dc486404 - languageName: node - linkType: hard - "@types/sockjs@npm:^0.3.36": version: 0.3.36 resolution: "@types/sockjs@npm:0.3.36" @@ -4451,12 +3503,12 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-basic-ssl@npm:2.3.0": - version: 2.3.0 - resolution: "@vitejs/plugin-basic-ssl@npm:2.3.0" +"@vitejs/plugin-basic-ssl@npm:2.0.0": + version: 2.0.0 + resolution: "@vitejs/plugin-basic-ssl@npm:2.0.0" peerDependencies: - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/7bc8e11350b87eafe6d58d2c9ae26c64e4456de88b8ed821eeea13ff640fe897e4096688b78f27bb56cca9fd43f5105e07462796a2bc9eb1c3a695dbf8a6f577 + vite: ^6.0.0 + checksum: 10/ccaf74939d1c1192cbbf9965a67f7ef0da11c83d62cbbdb8f33076428a1f9878f7ac933d73e3e3c7dc6706fcd78cc63b4e4364c296094abaeb48386868ccf5bd languageName: node linkType: hard @@ -4639,23 +3691,6 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10/e2f0c6a6708ad738b3e8f50233f4800de31ad41a6cdc50e0cbe51b76fed69fd0213516d92c15ce1a9985fca71a14606a9be22bf00f8475a58987b9bfb671c582 - languageName: node - linkType: hard - -"accepts@npm:^2.0.0": - version: 2.0.0 - resolution: "accepts@npm:2.0.0" - dependencies: - mime-types: "npm:^3.0.0" - negotiator: "npm:^1.0.0" - checksum: 10/ea1343992b40b2bfb3a3113fa9c3c2f918ba0f9197ae565c48d3f84d44b174f6b1d5cd9989decd7655963eb03a272abc36968cc439c2907f999bd5ef8653d5a7 - languageName: node - linkType: hard - "accepts@npm:~1.3.4, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -4666,25 +3701,7 @@ __metadata: languageName: node linkType: hard -"acorn-import-phases@npm:^1.0.3": - version: 1.0.4 - resolution: "acorn-import-phases@npm:1.0.4" - peerDependencies: - acorn: ^8.14.0 - checksum: 10/471050ac7d9b61909c837b426de9eeef2958997f6277ad7dea88d5894fd9b3245d8ed4a225c2ca44f814dbb20688009db7a80e525e8196fc9e98c5285b66161d - languageName: node - linkType: hard - -"acorn@npm:^8.15.0, acorn@npm:^8.16.0": - version: 8.16.0 - resolution: "acorn@npm:8.16.0" - bin: - acorn: bin/acorn - checksum: 10/690c673bb4d61b38ef82795fab58526471ad7f7e67c0e40c4ff1e10ecd80ce5312554ef633c9995bfc4e6d170cef165711f9ca9e49040b62c0c66fbf2dd3df2b - languageName: node - linkType: hard - -"acorn@npm:^8.8.2": +"acorn@npm:^8.14.0, acorn@npm:^8.8.2": version: 8.14.1 resolution: "acorn@npm:8.14.1" bin: @@ -4703,13 +3720,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:9.0.0": - version: 9.0.0 - resolution: "agent-base@npm:9.0.0" - checksum: 10/3a61414cd10dbb17fa8dae35124ffaa55fbb00f495004b2e7a8f4eca3a2b6ed9879474d4e2ebc27ee2f4207265652341525b4154e85c4d479be4854acd786bfb - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" @@ -4717,7 +3727,7 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:3.0.1, ajv-formats@npm:^3.0.1": +"ajv-formats@npm:3.0.1": version: 3.0.1 resolution: "ajv-formats@npm:3.0.1" dependencies: @@ -4756,19 +3766,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:8.18.0": - version: 8.18.0 - resolution: "ajv@npm:8.18.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10/bfed9de827a2b27c6d4084324eda76a4e32bdde27410b3e9b81d06e6f8f5c78370fc6b93fe1d869f1939ff1d7c4ae8896960995acb8425e3e9288c8884247c48 - languageName: node - linkType: hard - -"ajv@npm:^8.0.0, ajv@npm:^8.9.0": +"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.9.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -4780,40 +3778,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.17.1": - version: 8.20.0 - resolution: "ajv@npm:8.20.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - fast-uri: "npm:^3.0.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - checksum: 10/5ce59c0537f4c2aca9a758b412659ec70acb4d5dde971c10ecf21d2e3d799f99acdb4a08e1f5fb2e067c8542930398aae793bb996bb07d3feb81dae22fe2ada9 - languageName: node - linkType: hard - -"algoliasearch@npm:5.50.2": - version: 5.50.2 - resolution: "algoliasearch@npm:5.50.2" - dependencies: - "@algolia/abtesting": "npm:1.16.2" - "@algolia/client-abtesting": "npm:5.50.2" - "@algolia/client-analytics": "npm:5.50.2" - "@algolia/client-common": "npm:5.50.2" - "@algolia/client-insights": "npm:5.50.2" - "@algolia/client-personalization": "npm:5.50.2" - "@algolia/client-query-suggestions": "npm:5.50.2" - "@algolia/client-search": "npm:5.50.2" - "@algolia/ingestion": "npm:1.50.2" - "@algolia/monitoring": "npm:1.50.2" - "@algolia/recommend": "npm:5.50.2" - "@algolia/requester-browser-xhr": "npm:5.50.2" - "@algolia/requester-fetch": "npm:5.50.2" - "@algolia/requester-node-http": "npm:5.50.2" - checksum: 10/15ebbaf2d95e59c87412ea2a5df728c6cf4784eb41a274c3e8df60372393fd97bd859691c71f6598083867c2313c73f9e96e83dd171e6a8a48d90a72d28f1c4c - languageName: node - linkType: hard - "ansi-colors@npm:4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" @@ -4821,6 +3785,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10/8661034456193ffeda0c15c8c564a9636b0c04094b7f78bd01517929c17c504090a60f7a75f949f5af91289c264d3e1001d91492c1bd58efc8e100500ce04de2 + languageName: node + linkType: hard + "ansi-escapes@npm:^7.0.0": version: 7.0.0 resolution: "ansi-escapes@npm:7.0.0" @@ -4853,14 +3826,7 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^6.2.2": - version: 6.2.2 - resolution: "ansi-regex@npm:6.2.2" - checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0": +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" dependencies: @@ -4869,20 +3835,13 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": +"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": version: 6.2.1 resolution: "ansi-styles@npm:6.2.1" checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 languageName: node linkType: hard -"ansi-styles@npm:^6.2.3": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 - languageName: node - linkType: hard - "anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -4907,97 +3866,69 @@ __metadata: languageName: node linkType: hard -"asn1js@npm:^3.0.6": - version: 3.0.10 - resolution: "asn1js@npm:3.0.10" - dependencies: - pvtsutils: "npm:^1.3.6" - pvutils: "npm:^1.1.5" - tslib: "npm:^2.8.1" - checksum: 10/9cfbca89b1ac0f81aeba61c0af730d69f1214f0815eb1381ff6680f9b5bcb258cf0588f32175427faf1799eccc43d9111d1bbd98f0f01eb47af69413e4f85654 - languageName: node - linkType: hard - -"autoprefixer@npm:10.5.0": - version: 10.5.0 - resolution: "autoprefixer@npm:10.5.0" +"autoprefixer@npm:10.4.21": + version: 10.4.21 + resolution: "autoprefixer@npm:10.4.21" dependencies: - browserslist: "npm:^4.28.2" - caniuse-lite: "npm:^1.0.30001787" - fraction.js: "npm:^5.3.4" + browserslist: "npm:^4.24.4" + caniuse-lite: "npm:^1.0.30001702" + fraction.js: "npm:^4.3.7" + normalize-range: "npm:^0.1.2" picocolors: "npm:^1.1.1" postcss-value-parser: "npm:^4.2.0" peerDependencies: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 10/3398a70ad57dfeb4fc1dd6b344c99ab996002780cd777af853de0bcc442c0f31d8c3fc9ab3ba018a2f0d34f0c102ce7b75ad35bb5d7b50f2792a3b62899189b2 - languageName: node - linkType: hard - -"babel-loader@npm:10.1.1": - version: 10.1.1 - resolution: "babel-loader@npm:10.1.1" - dependencies: - find-up: "npm:^5.0.0" - peerDependencies: - "@babel/core": ^7.12.0 || ^8.0.0-beta.1 - "@rspack/core": ^1.0.0 || ^2.0.0-0 - webpack: ">=5.61.0" - peerDependenciesMeta: - "@rspack/core": - optional: true - webpack: - optional: true - checksum: 10/4169d55704e7b5204d79b628266d7820856602864044c7110893052e8a7c118a4cacb94bebfb33a6ca2e91b195e0359e6e214e6ef3c9e48137537b7624cc3338 + checksum: 10/5d7aeee78ef362a6838e12312908516a8ac5364414175273e5cff83bbff67612755b93d567f3aa01ce318342df48aeab4b291847b5800c780e58c458f61a98a6 languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.14, babel-plugin-polyfill-corejs2@npm:^0.4.15": - version: 0.4.17 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.17" +"babel-loader@npm:10.0.0": + version: 10.0.0 + resolution: "babel-loader@npm:10.0.0" dependencies: - "@babel/compat-data": "npm:^7.28.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.8" - semver: "npm:^6.3.1" + find-up: "npm:^5.0.0" peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/35796b7f960d2e90ae78e9eb60491550976b839bbb4ce4c060df822cce191e4b5d93f13f0e64c2ba3ffc6ab3d32d3ced3f84ec567cc141088a11fa5a1628265d + "@babel/core": ^7.12.0 + webpack: ">=5.61.0" + checksum: 10/f22dc803e38a6b29cc61fbc3482f1f42a8787df2a43706dc937d328103ba6b947a223f67706b07af765d415664ad56e9fed00f85b524fe223f3ac3f00b03770b languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.13.0": - version: 0.13.0 - resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" +"babel-plugin-polyfill-corejs2@npm:^0.4.10": + version: 0.4.12 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.12" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.5" - core-js-compat: "npm:^3.43.0" + "@babel/compat-data": "npm:^7.22.6" + "@babel/helper-define-polyfill-provider": "npm:^0.6.3" + semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/aa36f9a09521404dd0569a4cbd5f88aa4b9abff59508749abde5d09d66c746012fb94ed1e6e2c8be3710939a2a4c6293ee3be889125d7611c93e5897d9e5babd + checksum: 10/38b8cd69f0ba6a35f7f1cc08960f79fbc4572fe80e60aced719dab33a77c7872ee0faebc72da95852ae0d86df1aeaa54660bf309871db1934c5a4904f0744327 languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.14.0": - version: 0.14.2 - resolution: "babel-plugin-polyfill-corejs3@npm:0.14.2" +"babel-plugin-polyfill-corejs3@npm:^0.11.0": + version: 0.11.1 + resolution: "babel-plugin-polyfill-corejs3@npm:0.11.1" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.8" - core-js-compat: "npm:^3.48.0" + "@babel/helper-define-polyfill-provider": "npm:^0.6.3" + core-js-compat: "npm:^3.40.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/bb500bfec712eb5e8c9058dc299677a5325af7e09ebd725c89719f2f555eca3f2b1a8644137c8e67d7fc83d7be48a7189a1a385b61ed2cf63dbb64e79461b9ee + checksum: 10/19a2978ee3462cc3b98e7d36e6537bf9fb1fb61f42fd96cb41e9313f2ac6f2c62380d94064366431eff537f342184720fe9bce73eb65fd57c5311d15e8648f62 languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.6.5, babel-plugin-polyfill-regenerator@npm:^0.6.6": - version: 0.6.8 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.8" +"babel-plugin-polyfill-regenerator@npm:^0.6.1": + version: 0.6.3 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.3" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.8" + "@babel/helper-define-polyfill-provider": "npm:^0.6.3" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/974464353d6f974e97673385aff616a913c0b76039eab8c5317a2d07c661e080f3dcc213e86f3eae40010172a27ab793cda7a290a8a899716f9a22df9b1d92d2 + checksum: 10/d12696e6b3f280eb78fac551619ca4389262db62c7352cd54bf679d830df8b35596eef2de77cf00db6648eada1c99d49c4f40636dbc9c335a1e5420cfef96750 languageName: node linkType: hard @@ -5008,10 +3939,10 @@ __metadata: languageName: node linkType: hard -"balanced-match@npm:^4.0.2": - version: 4.0.4 - resolution: "balanced-match@npm:4.0.4" - checksum: 10/fb07bb66a0959c2843fc055838047e2a95ccebb837c519614afb067ebfdf2fa967ca8d712c35ced07f2cd26fc6f07964230b094891315ad74f11eba3d53178a0 +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 languageName: node linkType: hard @@ -5022,15 +3953,6 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:^2.10.12": - version: 2.10.21 - resolution: "baseline-browser-mapping@npm:2.10.21" - bin: - baseline-browser-mapping: dist/cli.cjs - checksum: 10/3bbf95e713eef02e2585843fea2792371cbcb6a2395ca2099c1818c10cfb5bce1b7a8aa84050306981748209791cb4442d1e5f3697fda88c36bb106f3d23043c - languageName: node - linkType: hard - "batch@npm:0.6.1": version: 0.6.1 resolution: "batch@npm:0.6.1" @@ -5038,20 +3960,19 @@ __metadata: languageName: node linkType: hard -"beasties@npm:0.4.2": - version: 0.4.2 - resolution: "beasties@npm:0.4.2" +"beasties@npm:0.3.3": + version: 0.3.3 + resolution: "beasties@npm:0.3.3" dependencies: - css-select: "npm:^6.0.0" - css-what: "npm:^7.0.0" + css-select: "npm:^5.1.0" + css-what: "npm:^6.1.0" dom-serializer: "npm:^2.0.0" domhandler: "npm:^5.0.3" htmlparser2: "npm:^10.0.0" picocolors: "npm:^1.1.1" postcss: "npm:^8.4.49" postcss-media-query-parser: "npm:^0.2.3" - postcss-safe-parser: "npm:^7.0.1" - checksum: 10/02fd763b42946e95181a7be42ba7f14e8c8c914b6a7aca83ff15d217a8adc0ba75e2c03586eab684b7caab2e903d4d63a714a4f688e25ae555754eda04d43df8 + checksum: 10/0efc47e9579e4a5e98329bbdadab93284db43a5718de372163b4c8bd89b4ce0d766a53a12019431b02165297c66eec06a5e17706811006e0021ae5d444b75185 languageName: node linkType: hard @@ -5069,7 +3990,18 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^1.19.0": +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10/b7904e66ed0bdfc813c06ea6c3e35eafecb104369dbf5356d0f416af90c1546de3b74e5b63506f0629acf5e16a6f87c3798f16233dcff086e9129383aa02ab55 + languageName: node + linkType: hard + +"body-parser@npm:1.20.3, body-parser@npm:^1.19.0": version: 1.20.3 resolution: "body-parser@npm:1.20.3" dependencies: @@ -5089,43 +4021,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^2.2.1": - version: 2.2.2 - resolution: "body-parser@npm:2.2.2" - dependencies: - bytes: "npm:^3.1.2" - content-type: "npm:^1.0.5" - debug: "npm:^4.4.3" - http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.7.0" - on-finished: "npm:^2.4.1" - qs: "npm:^6.14.1" - raw-body: "npm:^3.0.1" - type-is: "npm:^2.0.1" - checksum: 10/69671f67d4d5ae5974593901a92d639757231da1725ed6de4d35e86cde9ce7650afdf1cd28df9b6f7892ea7f9eb03ccb30c70fe27d679275ae4cb4aae5ce1b21 - languageName: node - linkType: hard - -"body-parser@npm:~1.20.3": - version: 1.20.5 - resolution: "body-parser@npm:1.20.5" - dependencies: - bytes: "npm:~3.1.2" - content-type: "npm:~1.0.5" - debug: "npm:2.6.9" - depd: "npm:2.0.0" - destroy: "npm:~1.2.0" - http-errors: "npm:~2.0.1" - iconv-lite: "npm:~0.4.24" - on-finished: "npm:~2.4.1" - qs: "npm:~6.15.1" - raw-body: "npm:~2.5.3" - type-is: "npm:~1.6.18" - unpipe: "npm:~1.0.0" - checksum: 10/3ec787c0d23b16542972226ee352ed917ae404bf862b6783040e8cfc994f165432f6d48e9341ef57f489c667c880f9c5174fad559c482607f83f4db7f412de3a - languageName: node - linkType: hard - "bonjour-service@npm:^1.2.1": version: 1.3.0 resolution: "bonjour-service@npm:1.3.0" @@ -5162,15 +4057,6 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^5.0.5": - version: 5.0.5 - resolution: "brace-expansion@npm:5.0.5" - dependencies: - balanced-match: "npm:^4.0.2" - checksum: 10/f259b2ddf04489da9512ad637ba6b4ef2d77abd4445d20f7f1714585f153435200a53fa6a2e4a5ee974df14ddad4cd16421f6f803e96e8b452bd48598878d0ee - languageName: node - linkType: hard - "braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" @@ -5180,7 +4066,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0": +"browserslist@npm:^4.21.5, browserslist@npm:^4.23.0, browserslist@npm:^4.24.0, browserslist@npm:^4.24.4": version: 4.24.4 resolution: "browserslist@npm:4.24.4" dependencies: @@ -5194,21 +4080,6 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.26.0, browserslist@npm:^4.28.1, browserslist@npm:^4.28.2": - version: 4.28.2 - resolution: "browserslist@npm:4.28.2" - dependencies: - baseline-browser-mapping: "npm:^2.10.12" - caniuse-lite: "npm:^1.0.30001782" - electron-to-chromium: "npm:^1.5.328" - node-releases: "npm:^2.0.36" - update-browserslist-db: "npm:^1.2.3" - bin: - browserslist: cli.js - checksum: 10/cff88386e5b5ba5614c9063bd32ef94865bba22b6a381844c7d09ea1eea62a2247e7106e516abdbfda6b75b9986044c991dfe45f92f10add5ad63dccc07589ec - languageName: node - linkType: hard - "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -5216,6 +4087,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 + languageName: node + linkType: hard + "bundle-name@npm:^4.1.0": version: 4.1.0 resolution: "bundle-name@npm:4.1.0" @@ -5225,21 +4106,14 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": +"bytes@npm:3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 languageName: node linkType: hard -"bytestreamjs@npm:^2.0.1": - version: 2.0.1 - resolution: "bytestreamjs@npm:2.0.1" - checksum: 10/523b1024e3f887cdc0b3db7c4fc14b8563aaeb75e6642a41991b3208277fd0ae9cd66003c73473fe706c42797bf0c3f1f498fb9880b431d75b332e5709d56a0c - languageName: node - linkType: hard - -"cacache@npm:^19.0.1": +"cacache@npm:^19.0.0, cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" dependencies: @@ -5259,24 +4133,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^20.0.0, cacache@npm:^20.0.1": - version: 20.0.4 - resolution: "cacache@npm:20.0.4" - dependencies: - "@npmcli/fs": "npm:^5.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^13.0.0" - lru-cache: "npm:^11.1.0" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^13.0.0" - checksum: 10/02c1b4c57dc2473e6f4654220c9405b73ae5fcdb392f82a7cf535468a52b842690cdb3694861d13bbe4dc067d5f8abe9697b4f791ae5b65cd73d62abad1e3e54 - languageName: node - linkType: hard - "call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 resolution: "call-bind-apply-helpers@npm:1.0.2" @@ -5304,24 +4160,34 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001688, caniuse-lite@npm:^1.0.30001782, caniuse-lite@npm:^1.0.30001787": - version: 1.0.30001790 - resolution: "caniuse-lite@npm:1.0.30001790" - checksum: 10/2625ba0b9c2648d14b4b02daf2fe7013d4efe087a45b034f40849c97077d435dbc610b47a34d3d6360cd62b7972864ae16978955205b7b8f7397303ba793e0ed +"caniuse-lite@npm:^1.0.30001688": + version: 1.0.30001702 + resolution: "caniuse-lite@npm:1.0.30001702" + checksum: 10/1f756953e5caf6c5b17562413cbbb7768b6b853620001b592aff07235ed98e84a1c169ad627d2cd77acd1524f482b5a9682edbd1b7b5cd3dae7753785d7b021d languageName: node linkType: hard -"chalk@npm:^5.6.2": - version: 5.6.2 - resolution: "chalk@npm:5.6.2" - checksum: 10/1b2f48f6fba1370670d5610f9cd54c391d6ede28f4b7062dd38244ea5768777af72e5be6b74fb6c6d54cb84c4a2dff3f3afa9b7cb5948f7f022cfd3d087989e0 +"caniuse-lite@npm:^1.0.30001702": + version: 1.0.30001707 + resolution: "caniuse-lite@npm:1.0.30001707" + checksum: 10/5c5f9aad651f4d957cc59c8b4ac22bb7ac3a1c86c26ee7d5c59b00062bdc1c421980513179da1f5e20cade2da8d7f3c41d482ce7d4a8d9f411e4a827fe092d29 languageName: node linkType: hard -"chardet@npm:^2.1.1": - version: 2.1.1 - resolution: "chardet@npm:2.1.1" - checksum: 10/d56913b65e45c5c86f331988e2ef6264c131bfeadaae098ee719bf6610546c77740e37221ffec802dde56b5e4466613a4c754786f4da6b5f6c5477243454d324 +"chalk@npm:^4.1.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 + languageName: node + linkType: hard + +"chardet@npm:^0.7.0": + version: 0.7.0 + resolution: "chardet@npm:0.7.0" + checksum: 10/b0ec668fba5eeec575ed2559a0917ba41a6481f49063c8445400e476754e0957ee09e44dc032310f526182b8f1bf25e9d4ed371f74050af7be1383e06bc44952 languageName: node linkType: hard @@ -5353,12 +4219,10 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^5.0.0": - version: 5.0.0 - resolution: "chokidar@npm:5.0.0" - dependencies: - readdirp: "npm:^5.0.0" - checksum: 10/a1c2a4ee6ee81ba6409712c295a47be055fb9de1186dfbab33c1e82f28619de962ba02fc5f9d433daaedc96c35747460d8b2079ac2907de2c95e3f7cce913113 +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10/c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f languageName: node linkType: hard @@ -5376,6 +4240,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: "npm:^3.1.0" + checksum: 10/2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + "cli-cursor@npm:^5.0.0": version: 5.0.0 resolution: "cli-cursor@npm:5.0.0" @@ -5385,20 +4258,20 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^3.2.0": - version: 3.4.0 - resolution: "cli-spinners@npm:3.4.0" - checksum: 10/6a4021c1999011fc34ae714f055dcdafb56309abc1f8fb021ea7d9370dfc524485fe8684226015e5fe6053dd30544e74270184ff7edc3fa4d37043b8efd0a054 +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 languageName: node linkType: hard -"cli-truncate@npm:^5.2.0": - version: 5.2.0 - resolution: "cli-truncate@npm:5.2.0" +"cli-truncate@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-truncate@npm:4.0.0" dependencies: - slice-ansi: "npm:^8.0.0" - string-width: "npm:^8.2.0" - checksum: 10/b789b6c2caff1560259aedeb6aaafcf41167d478df418d718a8c92edd6bc5a0ece272b8fb7e7911fbd31cef7b1ac8a30f2b21d90c3174b55a018fe3f2604a137 + slice-ansi: "npm:^5.0.0" + string-width: "npm:^7.0.0" + checksum: 10/d5149175fd25ca985731bdeec46a55ec237475cf74c1a5e103baea696aceb45e372ac4acbaabf1316f06bd62e348123060f8191ffadfeedebd2a70a2a7fb199d languageName: node linkType: hard @@ -5420,14 +4293,14 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^9.0.1": - version: 9.0.1 - resolution: "cliui@npm:9.0.1" +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" dependencies: - string-width: "npm:^7.2.0" - strip-ansi: "npm:^7.1.0" - wrap-ansi: "npm:^9.0.0" - checksum: 10/df43d8d1c6e3254cbb64b1905310d5f6672c595496a3cbe76946c6d24777136886470686f2772ac9edfe547a74bb70e8017530b3554715aee119efd7752fc0d9 + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 languageName: node linkType: hard @@ -5442,6 +4315,13 @@ __metadata: languageName: node linkType: hard +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 10/d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -5458,7 +4338,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.10": +"colorette@npm:^2.0.10, colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f @@ -5481,18 +4361,18 @@ __metadata: languageName: node linkType: hard -"compression@npm:^1.8.1": - version: 1.8.1 - resolution: "compression@npm:1.8.1" +"compression@npm:^1.7.4": + version: 1.8.0 + resolution: "compression@npm:1.8.0" dependencies: bytes: "npm:3.1.2" compressible: "npm:~2.0.18" debug: "npm:2.6.9" negotiator: "npm:~0.6.4" - on-headers: "npm:~1.1.0" + on-headers: "npm:~1.0.2" safe-buffer: "npm:5.2.1" vary: "npm:~1.1.2" - checksum: 10/e7552bfbd780f2003c6fe8decb44561f5cc6bc82f0c61e81122caff5ec656f37824084f52155b1e8ef31d7656cecbec9a2499b7a68e92e20780ffb39b479abb7 + checksum: 10/ca213b9bd03e56c7c3596399d846237b5f0b31ca4cdeaa76a9547cd3c1465fbcfcb0fe93a5d7ff64eff28383fc65b53f1ef8bb2720d11bb48ad8c0836c502506 languageName: node linkType: hard @@ -5522,14 +4402,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:^1.0.0": - version: 1.1.0 - resolution: "content-disposition@npm:1.1.0" - checksum: 10/c4f65e3c001a4a8eb87d0d24c0f112abb139836fb13b8ea67276715e7dce09570ef666ba7848ee8b660d467e6588d030c8ed7e8d0128db6ca78a0800dcd8c7a8 - languageName: node - linkType: hard - -"content-disposition@npm:~0.5.4": +"content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5538,7 +4411,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 @@ -5559,57 +4432,57 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:^1.2.1": - version: 1.2.2 - resolution: "cookie-signature@npm:1.2.2" - checksum: 10/be44a3c9a56f3771aea3a8bd8ad8f0a8e2679bcb967478267f41a510b4eb5ec55085386ba79c706c4ac21605ca76f4251973444b90283e0eb3eeafe8a92c7708 +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a languageName: node linkType: hard -"cookie-signature@npm:~1.0.6": - version: 1.0.7 - resolution: "cookie-signature@npm:1.0.7" - checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380 languageName: node linkType: hard -"cookie@npm:^0.7.1, cookie@npm:~0.7.1, cookie@npm:~0.7.2": +"cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f languageName: node linkType: hard -"copy-anything@npm:^3.0.5": - version: 3.0.5 - resolution: "copy-anything@npm:3.0.5" +"copy-anything@npm:^2.0.1": + version: 2.0.6 + resolution: "copy-anything@npm:2.0.6" dependencies: - is-what: "npm:^4.1.8" - checksum: 10/4c41385a94a1cff6352a954f9b1c05b6bb1b70713a2d31f4c7b188ae7187ce00ddcc9c09bd58d24cd35b67fc6dd84df5954c0be86ea10700ff74e677db3cb09c + is-what: "npm:^3.14.1" + checksum: 10/3b41be8f6322c2c13e93cde62a64d532f138f31d44ab85a3405d88601134afccc068be06534c162ed5c06b209788c423d7aaa50f1c34a92db81a1f8560d199eb languageName: node linkType: hard -"copy-webpack-plugin@npm:14.0.0": - version: 14.0.0 - resolution: "copy-webpack-plugin@npm:14.0.0" +"copy-webpack-plugin@npm:13.0.0": + version: 13.0.0 + resolution: "copy-webpack-plugin@npm:13.0.0" dependencies: glob-parent: "npm:^6.0.1" normalize-path: "npm:^3.0.0" schema-utils: "npm:^4.2.0" - serialize-javascript: "npm:^7.0.3" + serialize-javascript: "npm:^6.0.2" tinyglobby: "npm:^0.2.12" peerDependencies: webpack: ^5.1.0 - checksum: 10/734524b9569b873686bdd3addef77ac9604ecaf6eaadaf2fc37f1a91eb4a50c38e8d034899e65a189af37d29b1778b34b818493e5922dc1d089625ecd0f33211 + checksum: 10/209051dd3c0bc7ab97170309cdb1826e642044d2d53e0adc35bb227123c89ae1296a504409325e9b955d7b2d1a505b063f0023e924151d382dbcc92cb9325e6a languageName: node linkType: hard -"core-js-compat@npm:^3.43.0, core-js-compat@npm:^3.48.0": - version: 3.49.0 - resolution: "core-js-compat@npm:3.49.0" +"core-js-compat@npm:^3.40.0": + version: 3.41.0 + resolution: "core-js-compat@npm:3.41.0" dependencies: - browserslist: "npm:^4.28.1" - checksum: 10/eb35ad9b31a613092d32e5eb0c9fecb695e680bb29509fe04ae297ef790cea47d06864ef8939c8f5f189cce0bd2807fef8b2d6450f7eeb917ffaaf38a775dece + browserslist: "npm:^4.24.4" + checksum: 10/a59da111fc437cc7ed1a1448dae6883617cabebd7731433d27ad75e0ff77df5f411204979bd8eb5668d2600f99db46eedf6f87e123109b6de728bef489d4229a languageName: node linkType: hard @@ -5620,16 +4493,6 @@ __metadata: languageName: node linkType: hard -"cors@npm:^2.8.5": - version: 2.8.6 - resolution: "cors@npm:2.8.6" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10/aa7174305b21ceb90f9c84f4eaa32f04432d333addbfdc0d1eb7310393c48902e5364aada5ac2f5d054528d63b3179238444475426fcb74e1e345077de485727 - languageName: node - linkType: hard - "cors@npm:~2.8.5": version: 2.8.5 resolution: "cors@npm:2.8.5" @@ -5657,7 +4520,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -5668,47 +4531,47 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:7.1.4": - version: 7.1.4 - resolution: "css-loader@npm:7.1.4" +"css-loader@npm:7.1.2": + version: 7.1.2 + resolution: "css-loader@npm:7.1.2" dependencies: icss-utils: "npm:^5.1.0" - postcss: "npm:^8.4.40" + postcss: "npm:^8.4.33" postcss-modules-extract-imports: "npm:^3.1.0" postcss-modules-local-by-default: "npm:^4.0.5" postcss-modules-scope: "npm:^3.2.0" postcss-modules-values: "npm:^4.0.0" postcss-value-parser: "npm:^4.2.0" - semver: "npm:^7.6.3" + semver: "npm:^7.5.4" peerDependencies: - "@rspack/core": 0.x || ^1.0.0 || ^2.0.0-0 + "@rspack/core": 0.x || 1.x webpack: ^5.27.0 peerDependenciesMeta: "@rspack/core": optional: true webpack: optional: true - checksum: 10/4a37f3bd5db6d7d13a71f6ffff6d7afc3b243d4e2105c98dd1df9c1ae719cf95c3b46c47aeb6c5fd3675caf60bdb144e9baeba979a432df0906613e8f07b7623 + checksum: 10/ddde22fb103888320f60a1414a6a04638d2e9760a532a52d03c45e6e2830b32dd76c734aeef426f78dd95b2d15f77eeec3854ac53061aff02569732dc6e6801c languageName: node linkType: hard -"css-select@npm:^6.0.0": - version: 6.0.0 - resolution: "css-select@npm:6.0.0" +"css-select@npm:^5.1.0": + version: 5.1.0 + resolution: "css-select@npm:5.1.0" dependencies: boolbase: "npm:^1.0.0" - css-what: "npm:^7.0.0" - domhandler: "npm:^5.0.3" - domutils: "npm:^3.2.2" - nth-check: "npm:^2.1.1" - checksum: 10/361b07f67bdf2a30771a58adf3a7b1e8d4ba52a9e2a020c9fd0cd846e9a6b9149dc9a043b6fc02a6c07e47d58a3a606b97051ecddabddeadafc06a9e88c96e70 + css-what: "npm:^6.1.0" + domhandler: "npm:^5.0.2" + domutils: "npm:^3.0.1" + nth-check: "npm:^2.0.1" + checksum: 10/d486b1e7eb140468218a5ab5af53257e01f937d2173ac46981f6b7de9c5283d55427a36715dc8decfc0c079cf89259ac5b41ef58f6e1a422eee44ab8bfdc78da languageName: node linkType: hard -"css-what@npm:^7.0.0": - version: 7.0.0 - resolution: "css-what@npm:7.0.0" - checksum: 10/253c3013f8aee74b60d7d5c60bc62f3151a32f59ec6a9cc74527bb514d3aa746466f81fc10d57d85c58ee777d6e82eced7c5733a2181a884458dfc134518afab +"css-what@npm:^6.1.0": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: 10/c67a3a2d0d81843af87f8bf0a4d0845b0f952377714abbb2884e48942409d57a2110eabee003609d02ee487b054614bdfcfc59ee265728ff105bd5aa221c1d0e languageName: node linkType: hard @@ -5756,18 +4619,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.4.0, debug@npm:^4.4.3": - version: 4.4.3 - resolution: "debug@npm:4.4.3" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad - languageName: node - linkType: hard - "debug@npm:~4.3.1, debug@npm:~4.3.2, debug@npm:~4.3.4": version: 4.3.7 resolution: "debug@npm:4.3.7" @@ -5797,13 +4648,12 @@ __metadata: languageName: node linkType: hard -"default-browser@npm:^5.4.0": - version: 5.5.0 - resolution: "default-browser@npm:5.5.0" +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" dependencies: - bundle-name: "npm:^4.1.0" - default-browser-id: "npm:^5.0.0" - checksum: 10/c5c5d84a4abd82850e98f06798a55dee87fc1064538bea00cc14c0fb2dccccbff5e9e07eeea80385fa653202d5d92509838b4239d610ddfa1c76a04a1f65e767 + clone: "npm:^1.0.2" + checksum: 10/3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a languageName: node linkType: hard @@ -5814,7 +4664,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0, depd@npm:~2.0.0": +"depd@npm:2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -5828,7 +4678,7 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0, destroy@npm:~1.2.0": +"destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -5913,7 +4763,7 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^3.2.1, domutils@npm:^3.2.2": +"domutils@npm:^3.0.1, domutils@npm:^3.2.1": version: 3.2.2 resolution: "domutils@npm:3.2.2" dependencies: @@ -5949,13 +4799,6 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.328": - version: 1.5.344 - resolution: "electron-to-chromium@npm:1.5.344" - checksum: 10/0c75ba1c7f95a226c4a8422e79c1ffe25a0b173c3f478b0a883f25e284cdbcb1b7ca897621048c8a4ff76b120ff3bb443728187558099aaa35e8a43e10a6a1c1 - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.5.73": version: 1.5.112 resolution: "electron-to-chromium@npm:1.5.112" @@ -5991,13 +4834,6 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe - languageName: node - linkType: hard - "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -6005,6 +4841,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -6038,13 +4881,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.20.0": - version: 5.21.0 - resolution: "enhanced-resolve@npm:5.21.0" +"enhanced-resolve@npm:^5.17.1": + version: 5.18.1 + resolution: "enhanced-resolve@npm:5.18.1" dependencies: graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.3.3" - checksum: 10/234d37ddd5a190cb30527b8f7c4d440fa3c99a1247cccf60f74696c4f84c8b98a8178a7e97a7809ffcf1d31afb40c6a1d2805ccb19d9bbc8721ad6b4f8af4073 + tapable: "npm:^2.2.0" + checksum: 10/50e81c7fe2239fba5670ebce78a34709906ed3a79274aa416434f7307b252e0b7824d76a7dd403eca795571dc6afd9a44183fc45a68475e8f2fdfbae6e92fcc3 languageName: node linkType: hard @@ -6074,13 +4917,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:^8.0.0": - version: 8.0.0 - resolution: "entities@npm:8.0.0" - checksum: 10/d6e2ba75e444fb101ee2fbb07c839e687306c8a509426b75186619c19196f97c1db9932ca083f823c03e4a20e7407b654aa34de8cbb7770468e20fb2d4573a0e - languageName: node - linkType: hard - "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -6136,10 +4972,10 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^2.0.0": - version: 2.0.0 - resolution: "es-module-lexer@npm:2.0.0" - checksum: 10/b075855289b5f40ee496f3d7525c5c501d029c3da15c22298a0030d625bf36d1da0768b26278f7f4bada2a602459b505888e20b77c414fba5da5619b0e84dbd1 +"es-module-lexer@npm:^1.2.1": + version: 1.6.0 + resolution: "es-module-lexer@npm:1.6.0" + checksum: 10/807ee7020cc46a9c970c78cad1f2f3fc139877e5ebad7f66dbfbb124d451189ba1c48c1c632bd5f8ce1b8af2caef3fca340ba044a410fa890d17b080a59024bb languageName: node linkType: hard @@ -6152,45 +4988,44 @@ __metadata: languageName: node linkType: hard -"esbuild-wasm@npm:0.28.0": - version: 0.28.0 - resolution: "esbuild-wasm@npm:0.28.0" +"esbuild-wasm@npm:0.25.4": + version: 0.25.4 + resolution: "esbuild-wasm@npm:0.25.4" bin: esbuild: bin/esbuild - checksum: 10/08aed09140d5952207cb5f7b544af42c023b96d9bfa87eb0fcc8d6fe3b621f44c8bfa5bc25a6509f0c55e09a9c9dfec69279abeaa17a71f24a92bc5fed125584 - languageName: node - linkType: hard - -"esbuild@npm:0.28.0": - version: 0.28.0 - resolution: "esbuild@npm:0.28.0" - dependencies: - "@esbuild/aix-ppc64": "npm:0.28.0" - "@esbuild/android-arm": "npm:0.28.0" - "@esbuild/android-arm64": "npm:0.28.0" - "@esbuild/android-x64": "npm:0.28.0" - "@esbuild/darwin-arm64": "npm:0.28.0" - "@esbuild/darwin-x64": "npm:0.28.0" - "@esbuild/freebsd-arm64": "npm:0.28.0" - "@esbuild/freebsd-x64": "npm:0.28.0" - "@esbuild/linux-arm": "npm:0.28.0" - "@esbuild/linux-arm64": "npm:0.28.0" - "@esbuild/linux-ia32": "npm:0.28.0" - "@esbuild/linux-loong64": "npm:0.28.0" - "@esbuild/linux-mips64el": "npm:0.28.0" - "@esbuild/linux-ppc64": "npm:0.28.0" - "@esbuild/linux-riscv64": "npm:0.28.0" - "@esbuild/linux-s390x": "npm:0.28.0" - "@esbuild/linux-x64": "npm:0.28.0" - "@esbuild/netbsd-arm64": "npm:0.28.0" - "@esbuild/netbsd-x64": "npm:0.28.0" - "@esbuild/openbsd-arm64": "npm:0.28.0" - "@esbuild/openbsd-x64": "npm:0.28.0" - "@esbuild/openharmony-arm64": "npm:0.28.0" - "@esbuild/sunos-x64": "npm:0.28.0" - "@esbuild/win32-arm64": "npm:0.28.0" - "@esbuild/win32-ia32": "npm:0.28.0" - "@esbuild/win32-x64": "npm:0.28.0" + checksum: 10/6b8b254049e65817015d1c3d7e72fc23f5ab9c277df9a39414bdf4350e15462cb0812d2fa6157f14f59e463bedfe804ebc88d32022664b72dcdc158e7c81319a + languageName: node + linkType: hard + +"esbuild@npm:0.25.4": + version: 0.25.4 + resolution: "esbuild@npm:0.25.4" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.4" + "@esbuild/android-arm": "npm:0.25.4" + "@esbuild/android-arm64": "npm:0.25.4" + "@esbuild/android-x64": "npm:0.25.4" + "@esbuild/darwin-arm64": "npm:0.25.4" + "@esbuild/darwin-x64": "npm:0.25.4" + "@esbuild/freebsd-arm64": "npm:0.25.4" + "@esbuild/freebsd-x64": "npm:0.25.4" + "@esbuild/linux-arm": "npm:0.25.4" + "@esbuild/linux-arm64": "npm:0.25.4" + "@esbuild/linux-ia32": "npm:0.25.4" + "@esbuild/linux-loong64": "npm:0.25.4" + "@esbuild/linux-mips64el": "npm:0.25.4" + "@esbuild/linux-ppc64": "npm:0.25.4" + "@esbuild/linux-riscv64": "npm:0.25.4" + "@esbuild/linux-s390x": "npm:0.25.4" + "@esbuild/linux-x64": "npm:0.25.4" + "@esbuild/netbsd-arm64": "npm:0.25.4" + "@esbuild/netbsd-x64": "npm:0.25.4" + "@esbuild/openbsd-arm64": "npm:0.25.4" + "@esbuild/openbsd-x64": "npm:0.25.4" + "@esbuild/sunos-x64": "npm:0.25.4" + "@esbuild/win32-arm64": "npm:0.25.4" + "@esbuild/win32-ia32": "npm:0.25.4" + "@esbuild/win32-x64": "npm:0.25.4" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -6234,8 +5069,6 @@ __metadata: optional: true "@esbuild/openbsd-x64": optional: true - "@esbuild/openharmony-arm64": - optional: true "@esbuild/sunos-x64": optional: true "@esbuild/win32-arm64": @@ -6246,40 +5079,39 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/49eafc8906cc4a760a1704556bd3b301f808fcdcf2725190383f151741226bf2a2898a03da75a06a896d6217dadc4f3f3168983557ee31bae602e2e37779a83a - languageName: node - linkType: hard - -"esbuild@npm:^0.27.0": - version: 0.27.7 - resolution: "esbuild@npm:0.27.7" - dependencies: - "@esbuild/aix-ppc64": "npm:0.27.7" - "@esbuild/android-arm": "npm:0.27.7" - "@esbuild/android-arm64": "npm:0.27.7" - "@esbuild/android-x64": "npm:0.27.7" - "@esbuild/darwin-arm64": "npm:0.27.7" - "@esbuild/darwin-x64": "npm:0.27.7" - "@esbuild/freebsd-arm64": "npm:0.27.7" - "@esbuild/freebsd-x64": "npm:0.27.7" - "@esbuild/linux-arm": "npm:0.27.7" - "@esbuild/linux-arm64": "npm:0.27.7" - "@esbuild/linux-ia32": "npm:0.27.7" - "@esbuild/linux-loong64": "npm:0.27.7" - "@esbuild/linux-mips64el": "npm:0.27.7" - "@esbuild/linux-ppc64": "npm:0.27.7" - "@esbuild/linux-riscv64": "npm:0.27.7" - "@esbuild/linux-s390x": "npm:0.27.7" - "@esbuild/linux-x64": "npm:0.27.7" - "@esbuild/netbsd-arm64": "npm:0.27.7" - "@esbuild/netbsd-x64": "npm:0.27.7" - "@esbuild/openbsd-arm64": "npm:0.27.7" - "@esbuild/openbsd-x64": "npm:0.27.7" - "@esbuild/openharmony-arm64": "npm:0.27.7" - "@esbuild/sunos-x64": "npm:0.27.7" - "@esbuild/win32-arm64": "npm:0.27.7" - "@esbuild/win32-ia32": "npm:0.27.7" - "@esbuild/win32-x64": "npm:0.27.7" + checksum: 10/227ffe9b31f0b184a0b0a0210bb9d32b2b115b8c5c9b09f08db2c3928cb470fc55a22dbba3c2894365d3abcc62c2089b85638be96a20691d1234d31990ea01b2 + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.0 + resolution: "esbuild@npm:0.25.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.0" + "@esbuild/android-arm": "npm:0.25.0" + "@esbuild/android-arm64": "npm:0.25.0" + "@esbuild/android-x64": "npm:0.25.0" + "@esbuild/darwin-arm64": "npm:0.25.0" + "@esbuild/darwin-x64": "npm:0.25.0" + "@esbuild/freebsd-arm64": "npm:0.25.0" + "@esbuild/freebsd-x64": "npm:0.25.0" + "@esbuild/linux-arm": "npm:0.25.0" + "@esbuild/linux-arm64": "npm:0.25.0" + "@esbuild/linux-ia32": "npm:0.25.0" + "@esbuild/linux-loong64": "npm:0.25.0" + "@esbuild/linux-mips64el": "npm:0.25.0" + "@esbuild/linux-ppc64": "npm:0.25.0" + "@esbuild/linux-riscv64": "npm:0.25.0" + "@esbuild/linux-s390x": "npm:0.25.0" + "@esbuild/linux-x64": "npm:0.25.0" + "@esbuild/netbsd-arm64": "npm:0.25.0" + "@esbuild/netbsd-x64": "npm:0.25.0" + "@esbuild/openbsd-arm64": "npm:0.25.0" + "@esbuild/openbsd-x64": "npm:0.25.0" + "@esbuild/sunos-x64": "npm:0.25.0" + "@esbuild/win32-arm64": "npm:0.25.0" + "@esbuild/win32-ia32": "npm:0.25.0" + "@esbuild/win32-x64": "npm:0.25.0" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -6323,8 +5155,6 @@ __metadata: optional: true "@esbuild/openbsd-x64": optional: true - "@esbuild/openharmony-arm64": - optional: true "@esbuild/sunos-x64": optional: true "@esbuild/win32-arm64": @@ -6335,7 +5165,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/262b16c4a33cb70e9f054759a7ce420541649315eef7b064172c795021ccce322e56c3f5fd52e8842873f1c23745f3ab62311a24860950bd5406ba77b36b8529 + checksum: 10/451daf6a442df29ec5d528587caa4ce783d41ff4acb93252da5a852b8d36c22e9f84d17f6721d4fbef9a1ba9855bc9fe1f167dd732c11665fe53032f2b89f114 languageName: node linkType: hard @@ -6346,7 +5176,7 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": +"escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 @@ -6393,7 +5223,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:^1.8.1, etag@npm:~1.8.1": +"etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff @@ -6407,10 +5237,10 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^5.0.4": - version: 5.0.4 - resolution: "eventemitter3@npm:5.0.4" - checksum: 10/54f5c8c543650d65f92d03dbef1bb73a682a920490c44699ad8f863a6b19bbca42fb7409aa09ca09cb98a44149d9a7bc1dffd55ca88a740bd928c7be0ad666a0 +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10/ac6423ec31124629c84c7077eed1e6987f6d66c31cf43c6fcbf6c87791d56317ce808d9ead483652436df171b526fc7220eccdc9f3225df334e81582c3cf7dd5 languageName: node linkType: hard @@ -6421,22 +5251,6 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1": - version: 3.0.8 - resolution: "eventsource-parser@npm:3.0.8" - checksum: 10/286a84a7005e3e669e94dce0bb48f00acfda0d3973671ae2790e48a93f7b27a85b1828df8f3876c70afe4d577b6a7e4f8794cb596072585b584b4f207bc35c15 - languageName: node - linkType: hard - -"eventsource@npm:^3.0.2": - version: 3.0.7 - resolution: "eventsource@npm:3.0.7" - dependencies: - eventsource-parser: "npm:^3.0.1" - checksum: 10/e034915bc97068d1d38617951afd798e6776d6a3a78e36a7569c235b177c7afc2625c9fe82656f7341ab72c7eeecb3fd507b7f88e9328f2448872ff9c4742bb6 - languageName: node - linkType: hard - "exponential-backoff@npm:^3.1.1": version: 3.1.2 resolution: "exponential-backoff@npm:3.1.2" @@ -6444,89 +5258,42 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^8.2.1": - version: 8.4.1 - resolution: "express-rate-limit@npm:8.4.1" - dependencies: - ip-address: "npm:10.1.0" - peerDependencies: - express: ">= 4.11" - checksum: 10/2f979a261e6c95e31a62f2ce1cb9621566b0061706d0337091c8399617d6f09b28402ba4383aa3d72518374a7eec35be24f9703de59e563cc127ba14e2748a4d - languageName: node - linkType: hard - -"express@npm:^4.22.1": - version: 4.22.1 - resolution: "express@npm:4.22.1" +"express@npm:^4.21.2": + version: 4.21.2 + resolution: "express@npm:4.21.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:~1.20.3" - content-disposition: "npm:~0.5.4" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:~0.7.1" - cookie-signature: "npm:~1.0.6" + cookie: "npm:0.7.1" + cookie-signature: "npm:1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:~1.3.1" - fresh: "npm:~0.5.2" - http-errors: "npm:~2.0.0" + finalhandler: "npm:1.3.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" - on-finished: "npm:~2.4.1" + on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:~0.1.12" + path-to-regexp: "npm:0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:~6.14.0" + qs: "npm:6.13.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:~0.19.0" - serve-static: "npm:~1.16.2" + send: "npm:0.19.0" + serve-static: "npm:1.16.2" setprototypeof: "npm:1.2.0" - statuses: "npm:~2.0.1" + statuses: "npm:2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470 - languageName: node - linkType: hard - -"express@npm:^5.2.1": - version: 5.2.1 - resolution: "express@npm:5.2.1" - dependencies: - accepts: "npm:^2.0.0" - body-parser: "npm:^2.2.1" - content-disposition: "npm:^1.0.0" - content-type: "npm:^1.0.5" - cookie: "npm:^0.7.1" - cookie-signature: "npm:^1.2.1" - debug: "npm:^4.4.0" - depd: "npm:^2.0.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - finalhandler: "npm:^2.1.0" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - merge-descriptors: "npm:^2.0.0" - mime-types: "npm:^3.0.0" - on-finished: "npm:^2.4.1" - once: "npm:^1.4.0" - parseurl: "npm:^1.3.3" - proxy-addr: "npm:^2.0.7" - qs: "npm:^6.14.0" - range-parser: "npm:^1.2.1" - router: "npm:^2.2.0" - send: "npm:^1.1.0" - serve-static: "npm:^2.2.0" - statuses: "npm:^2.0.1" - type-is: "npm:^2.0.1" - vary: "npm:^1.1.2" - checksum: 10/4aa545d89702ac83f645c77abda1b57bcabe288f0b380fb5580fac4e323ea0eb533005c8e666b4e19152fb16d4abf11ba87b22aa9a10857a0485cd86b94639bd + checksum: 10/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1 languageName: node linkType: hard @@ -6537,6 +5304,17 @@ __metadata: languageName: node linkType: hard +"external-editor@npm:^3.1.0": + version: 3.1.0 + resolution: "external-editor@npm:3.1.0" + dependencies: + chardet: "npm:^0.7.0" + iconv-lite: "npm:^0.4.24" + tmp: "npm:^0.0.33" + checksum: 10/776dff1d64a1d28f77ff93e9e75421a81c062983fd1544279d0a32f563c0b18c52abbb211f31262e2827e48edef5c9dc8f960d06dd2d42d1654443b88568056b + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -6544,19 +5322,16 @@ __metadata: languageName: node linkType: hard -"fast-string-truncated-width@npm:^3.0.2": - version: 3.0.3 - resolution: "fast-string-truncated-width@npm:3.0.3" - checksum: 10/3a1631e48927cb558b612a90ee78a61a660823c39b024bfc113935760b5b64805dbf03c4e696c33005294db578417687432e9d13567f1a582c2c75015e8a7648 - languageName: node - linkType: hard - -"fast-string-width@npm:^3.0.2": - version: 3.0.2 - resolution: "fast-string-width@npm:3.0.2" +"fast-glob@npm:3.3.3": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" dependencies: - fast-string-truncated-width: "npm:^3.0.2" - checksum: 10/5b9019769f2b00b96d43575c202f4e035a0e55eba7669a9a32351de9fa0805d0959a2afcaec6e4db5ee9b9a4c08d8e77f95abeb04b5bae2f76635cf04ddb4b80 + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10/dcc6432b269762dd47381d8b8358bf964d8f4f60286ac6aa41c01ade70bda459ff2001b516690b96d5365f68a49242966112b5d5cc9cd82395fa8f9d017c90ad languageName: node linkType: hard @@ -6567,12 +5342,12 @@ __metadata: languageName: node linkType: hard -"fast-wrap-ansi@npm:^0.2.0": - version: 0.2.0 - resolution: "fast-wrap-ansi@npm:0.2.0" +"fastq@npm:^1.6.0": + version: 1.19.1 + resolution: "fastq@npm:1.19.1" dependencies: - fast-string-width: "npm:^3.0.2" - checksum: 10/e717a249dae84c9a964e6b5da05c373fadd92714b2afb2d6c7e6f766c3409c773c95b28e186dcdd397e2d7850533dbdd766845d0cd29e15d172d33128f9447d3 + reusify: "npm:^1.0.4" + checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 languageName: node linkType: hard @@ -6597,15 +5372,15 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.5.0": - version: 6.5.0 - resolution: "fdir@npm:6.5.0" +"fdir@npm:^6.4.4": + version: 6.4.4 + resolution: "fdir@npm:6.4.4" peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1 + checksum: 10/d0000d6b790059b35f4ed19acc8847a66452e0bc68b28766c929ffd523e5ec2083811fc8a545e4a1d4945ce70e887b3a610c145c681073b506143ae3076342ed languageName: node linkType: hard @@ -6633,32 +5408,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:^2.1.0": - version: 2.1.1 - resolution: "finalhandler@npm:2.1.1" - dependencies: - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - checksum: 10/f4ba75c23408d8f9d393c3e875b9452e84d68c925411a6e67b7efa678b0bed5075ef33def4bb65ed8e0dd37c92a3ea354bcbde07303cd4dc2550e12b95885067 - languageName: node - linkType: hard - -"finalhandler@npm:~1.3.1": - version: 1.3.2 - resolution: "finalhandler@npm:1.3.2" +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" dependencies: debug: "npm:2.6.9" encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" - on-finished: "npm:~2.4.1" + on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - statuses: "npm:~2.0.2" + statuses: "npm:2.0.1" unpipe: "npm:~1.0.0" - checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed + checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b languageName: node linkType: hard @@ -6715,21 +5476,14 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^5.3.4": - version: 5.3.4 - resolution: "fraction.js@npm:5.3.4" - checksum: 10/ef2c4bc81b2484065f8f7e4c2498f3fdfe6d233b8e7c7f75e3683ed10698536129b2c2dbd6c3f788ca4a020ec07116dd909a91036a364c98dc802b5003bfc613 - languageName: node - linkType: hard - -"fresh@npm:^2.0.0": - version: 2.0.0 - resolution: "fresh@npm:2.0.0" - checksum: 10/44e1468488363074641991c1340d2a10c5a6f6d7c353d89fd161c49d120c58ebf9890720f7584f509058385836e3ce50ddb60e9f017315a4ba8c6c3461813bfc +"fraction.js@npm:^4.3.7": + version: 4.3.7 + resolution: "fraction.js@npm:4.3.7" + checksum: 10/bb5ebcdeeffcdc37b68ead3bdfc244e68de188e0c64e9702197333c72963b95cc798883ad16adc21588088b942bca5b6a6ff4aeb1362d19f6f3b629035dc15f5 languageName: node linkType: hard -"fresh@npm:~0.5.2": +"fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 @@ -6747,6 +5501,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/03191781e94bc9a54bd376d3146f90fe8e082627c502185dbf7b9b3032f66b0b142c1115f3b2cc5936575fc1b44845ce903dd4c21bec2a8d69f3bd56f9cee9ec + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -6810,13 +5573,6 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": - version: 1.5.0 - resolution: "get-east-asian-width@npm:1.5.0" - checksum: 10/60bc34cd1e975055ab99f0f177e31bed3e516ff7cee9c536474383954a976abaa6b94a51d99ad158ef1e372790fa096cab7d07f166bb0778f6587954c0fbe946 - languageName: node - linkType: hard - "get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -6845,16 +5601,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^6.0.1": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10/c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 - languageName: node - linkType: hard - -"glob-parent@npm:~5.1.2": +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -6863,12 +5610,12 @@ __metadata: languageName: node linkType: hard -"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": - version: 1.2.0 - resolution: "glob-to-regex.js@npm:1.2.0" - peerDependencies: - tslib: 2 - checksum: 10/13034e642db479d75448bdd9f37de7451bef2879c394bfe3f8df6588e0479893e94059eaee77cdf50dce675607fb2395c132dcca0c9a559a6192e89b2ad0f134 +"glob-parent@npm:^6.0.1": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10/c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 languageName: node linkType: hard @@ -6895,17 +5642,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^13.0.0": - version: 13.0.6 - resolution: "glob@npm:13.0.6" - dependencies: - minimatch: "npm:^10.2.2" - minipass: "npm:^7.1.3" - path-scurry: "npm:^2.0.2" - checksum: 10/201ad69e5f0aa74e1d8c00a481581f8b8c804b6a4fbfabeeb8541f5d756932800331daeba99b58fb9e4cd67e12ba5a7eba5b82fb476691588418060b84353214 - languageName: node - linkType: hard - "glob@npm:^7.1.3, glob@npm:^7.1.7, glob@npm:^7.2.0": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -6980,19 +5716,12 @@ __metadata: languageName: node linkType: hard -"hono@npm:^4.11.4": - version: 4.12.15 - resolution: "hono@npm:4.12.15" - checksum: 10/068d3c5cc33f63acc6dd8b7ce29d1415e8a09f63c571138d7a7db4ab8bccf4d32bb6d5149786bc7f4f4eaed613650f541c1b2a8ddf75639cf8cf8c8977109136 - languageName: node - linkType: hard - -"hosted-git-info@npm:^9.0.0": - version: 9.0.2 - resolution: "hosted-git-info@npm:9.0.2" +"hosted-git-info@npm:^8.0.0": + version: 8.0.2 + resolution: "hosted-git-info@npm:8.0.2" dependencies: - lru-cache: "npm:^11.1.0" - checksum: 10/0619c284ca7fc35322735e03fece90ed3ded67a2cf68e855e688d1bffd47078515d98ab8dff4bd08fb78d68d1a72ab8892180e15c7f23f24c922a6dfa601dbad + lru-cache: "npm:^10.0.1" + checksum: 10/dae9b04bf01efdb353d7b167fd257b95a48bb1a233a47b57c277b5ec8326dafc3c06a3999b92d1662fc3176cc2d6ea77db00b9f16195161c6c74f2e88e393721 languageName: node linkType: hard @@ -7054,19 +5783,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.0, http-errors@npm:~2.0.1": - version: 2.0.1 - resolution: "http-errors@npm:2.0.1" - dependencies: - depd: "npm:~2.0.0" - inherits: "npm:~2.0.4" - setprototypeof: "npm:~1.2.0" - statuses: "npm:~2.0.2" - toidentifier: "npm:~1.0.1" - checksum: 10/9fe31bc0edf36566c87048aed1d3d0cbe03552564adc3541626a0613f542d753fbcb13bdfcec0a3a530dbe1714bb566c89d46244616b66bddd26ac413b06a207 - languageName: node - linkType: hard - "http-errors@npm:~1.6.2": version: 1.6.3 resolution: "http-errors@npm:1.6.3" @@ -7110,9 +5826,9 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.9": - version: 2.0.9 - resolution: "http-proxy-middleware@npm:2.0.9" +"http-proxy-middleware@npm:^2.0.7": + version: 2.0.7 + resolution: "http-proxy-middleware@npm:2.0.7" dependencies: "@types/http-proxy": "npm:^1.17.8" http-proxy: "npm:^1.18.1" @@ -7124,7 +5840,7 @@ __metadata: peerDependenciesMeta: "@types/express": optional: true - checksum: 10/4ece416a91d52e96f8136c5f4abfbf7ac2f39becbad21fa8b158a12d7e7d8f808287ff1ae342b903fd1f15f2249dee87fabc09e1f0e73106b83331c496d67660 + checksum: 10/4a51bf612b752ad945701995c1c029e9501c97e7224c0cf3f8bf6d48d172d6a8f2b57c20fec469534fdcac3aa8a6f332224a33c6b0d7f387aa2cfff9b67216fd languageName: node linkType: hard @@ -7139,17 +5855,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:9.0.0": - version: 9.0.0 - resolution: "https-proxy-agent@npm:9.0.0" - dependencies: - agent-base: "npm:9.0.0" - debug: "npm:^4.3.4" - checksum: 10/27457d671278c8c1074cc901fe305b70d1e340127433219124c4aefc44153a179a8921e4b16d67beb2868a3a39b6b7ec84d91d8f24f2ec1d39cf4ac385351a92 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": +"https-proxy-agent@npm:7.0.6, https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" dependencies: @@ -7166,7 +5872,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24, iconv-lite@npm:~0.4.24": +"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -7184,15 +5890,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": - version: 0.7.2 - resolution: "iconv-lite@npm:0.7.2" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10/24c937b532f868e938386b62410b303b7c767ce3d08dc2829cbe59464d5a26ef86ae5ad1af6b34eec43ddfea39e7d101638644b0178d67262fa87015d59f983a - languageName: node - linkType: hard - "icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": version: 5.1.0 resolution: "icss-utils@npm:5.1.0" @@ -7202,12 +5899,19 @@ __metadata: languageName: node linkType: hard -"ignore-walk@npm:^8.0.0": - version: 8.0.0 - resolution: "ignore-walk@npm:8.0.0" +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 + languageName: node + linkType: hard + +"ignore-walk@npm:^7.0.0": + version: 7.0.0 + resolution: "ignore-walk@npm:7.0.0" dependencies: - minimatch: "npm:^10.0.3" - checksum: 10/694a66d481ca7073a85569d9751c0fcc4e4e0e08f69ba7e5bceed5ac3eef9bfa9184585327053be612022ea961033bfad1003d66058efdfb55bfab07dff23bba + minimatch: "npm:^9.0.0" + checksum: 10/b9793b009588f2cd6e813f65a424aa362facb182745058858073cc126b339154dea07b89bc8d6cd2aece6528390319a7534b475dd2da6e27dfbff6d049d5fe4b languageName: node linkType: hard @@ -7220,10 +5924,10 @@ __metadata: languageName: node linkType: hard -"immutable@npm:^5.1.5": - version: 5.1.5 - resolution: "immutable@npm:5.1.5" - checksum: 10/7aec2740239772ec8e92e793c991bd809203a97694f4ff3a18e50e28f9a6b02393ad033d87b458037bdf8c0ea37d4446d640e825f6171df3405cf6cf300ce028 +"immutable@npm:^5.0.2": + version: 5.0.3 + resolution: "immutable@npm:5.0.3" + checksum: 10/9aca1c783951bb204d7036fbcefac6dd42e7c8ad77ff54b38c5fc0924e6e16ce2d123c95db47c1170ba63dd3f6fc7aa74a29be7adef984031936c4cd1e9e8554 languageName: node linkType: hard @@ -7254,7 +5958,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -7268,17 +5972,10 @@ __metadata: languageName: node linkType: hard -"ini@npm:6.0.0, ini@npm:^6.0.0": - version: 6.0.0 - resolution: "ini@npm:6.0.0" - checksum: 10/e87d8cde86d091ddb104580d42dfdc8306593627269990ca0f5176ccc60c936268bad56856398fef924cdf0af33b1a9c21e84f85914820037e003ee45443cc85 - languageName: node - linkType: hard - -"ip-address@npm:10.1.0": - version: 10.1.0 - resolution: "ip-address@npm:10.1.0" - checksum: 10/a6979629d1ad9c1fb424bc25182203fad739b40225aebc55ec6243bbff5035faf7b9ed6efab3a097de6e713acbbfde944baacfa73e11852bb43989c45a68d79e +"ini@npm:5.0.0, ini@npm:^5.0.0": + version: 5.0.0 + resolution: "ini@npm:5.0.0" + checksum: 10/76e5567b46504b2b12650878ba6277204500a6ead3fe69eef419ee570456b364b39c040ee545846053f6d8a15797a82fc6d9efe06e392b9b6093935f4a2f2c30 languageName: node linkType: hard @@ -7322,7 +6019,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.16.1": +"is-core-module@npm:^2.16.0": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -7354,6 +6051,13 @@ __metadata: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^4.0.0": + version: 4.0.0 + resolution: "is-fullwidth-code-point@npm:4.0.0" + checksum: 10/8ae89bf5057bdf4f57b346fb6c55e9c3dd2549983d54191d722d5c739397a903012cc41a04ee3403fd872e811243ef91a7c5196da7b5841dc6b6aae31a264a8d + languageName: node + linkType: hard + "is-fullwidth-code-point@npm:^5.0.0": version: 5.0.0 resolution: "is-fullwidth-code-point@npm:5.0.0" @@ -7363,15 +6067,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^5.1.0": - version: 5.1.0 - resolution: "is-fullwidth-code-point@npm:5.1.0" - dependencies: - get-east-asian-width: "npm:^1.3.1" - checksum: 10/4700d8a82cb71bd2a2955587b2823c36dc4660eadd4047bfbd070821ddbce8504fc5f9b28725567ecddf405b1e06c6692c9b719f65df6af9ec5262bc11393a6a - languageName: node - linkType: hard - "is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -7381,13 +6076,6 @@ __metadata: languageName: node linkType: hard -"is-in-ssh@npm:^1.0.0": - version: 1.0.0 - resolution: "is-in-ssh@npm:1.0.0" - checksum: 10/d55cb39afdbca0cdc94cd493da7819c00d35048ea04fc1624aabde6e0c86aa6b91ddb38b2baf73c4b5d53cc8fbf1a8dfbf2e315594a808474b751ffb6b0d3e58 - languageName: node - linkType: hard - "is-inside-container@npm:^1.0.0": version: 1.0.0 resolution: "is-inside-container@npm:1.0.0" @@ -7399,10 +6087,10 @@ __metadata: languageName: node linkType: hard -"is-interactive@npm:^2.0.0": - version: 2.0.0 - resolution: "is-interactive@npm:2.0.0" - checksum: 10/e8d52ad490bed7ae665032c7675ec07732bbfe25808b0efbc4d5a76b1a1f01c165f332775c63e25e9a03d319ebb6b24f571a9e902669fc1e40b0a60b5be6e26c +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 10/824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 languageName: node linkType: hard @@ -7443,13 +6131,6 @@ __metadata: languageName: node linkType: hard -"is-promise@npm:^4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10/0b46517ad47b00b6358fd6553c83ec1f6ba9acd7ffb3d30a0bf519c5c69e7147c132430452351b8a9fc198f8dd6c4f76f8e6f5a7f100f8c77d57d9e0f4261a8a - languageName: node - linkType: hard - "is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" @@ -7462,17 +6143,17 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^2.0.0, is-unicode-supported@npm:^2.1.0": - version: 2.1.0 - resolution: "is-unicode-supported@npm:2.1.0" - checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10/a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 languageName: node linkType: hard -"is-what@npm:^4.1.8": - version: 4.1.16 - resolution: "is-what@npm:4.1.16" - checksum: 10/f6400634bae77be6903365dc53817292e1c4d8db1b467515d0c842505b8388ee8e558326d5e6952cb2a9d74116eca2af0c6adb8aa7e9d5c845a130ce9328bf13 +"is-what@npm:^3.14.1": + version: 3.14.1 + resolution: "is-what@npm:3.14.1" + checksum: 10/249beb4a8c1729c80ed24fa8527835301c8c70d2fa99706a301224576e0650df61edd7a0a8853999bf5fbe2c551f07148d2c3535260772e05a4c373d3d5362e1 languageName: node linkType: hard @@ -7513,13 +6194,6 @@ __metadata: languageName: node linkType: hard -"isexe@npm:^4.0.0": - version: 4.0.0 - resolution: "isexe@npm:4.0.0" - checksum: 10/2ead327ef596042ef9c9ec5f236b316acfaedb87f4bb61b3c3d574fb2e9c8a04b67305e04733bde52c24d9622fdebd3270aadb632adfbf9cadef88fe30f479e5 - languageName: node - linkType: hard - "isobject@npm:^3.0.1": version: 3.0.1 resolution: "isobject@npm:3.0.1" @@ -7630,19 +6304,12 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^2.5.1": - version: 2.6.1 - resolution: "jiti@npm:2.6.1" +"jiti@npm:^1.20.0": + version: 1.21.7 + resolution: "jiti@npm:1.21.7" bin: - jiti: lib/jiti-cli.mjs - checksum: 10/8cd72c5fd03a0502564c3f46c49761090f6dadead21fa191b73535724f095ad86c2fa89ee6fe4bc3515337e8d406cc8fb2d37b73fa0c99a34584bac35cd4a4de - languageName: node - linkType: hard - -"jose@npm:^6.1.3": - version: 6.2.2 - resolution: "jose@npm:6.2.2" - checksum: 10/fd66f24916d2507dbde0ca77ede86377e7d7eae42c7f94db0dfd014b0c6ce5041365dc8e757a44e54cd7606ea5e9c757aa544b51e55cb8a736e83320db1b8258 + jiti: bin/jiti.js + checksum: 10/6a182521532126e4b7b5ad64b64fb2e162718fc03bc6019c21aa2222aacde6c6dfce4fc3bce9f69561a73b24ab5f79750ad353c37c3487a220d5869a39eae3a2 languageName: node linkType: hard @@ -7671,7 +6338,7 @@ __metadata: languageName: node linkType: hard -"jsesc@npm:^3.0.2, jsesc@npm:~3.1.0": +"jsesc@npm:^3.0.2": version: 3.1.0 resolution: "jsesc@npm:3.1.0" bin: @@ -7689,17 +6356,17 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0": +"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" checksum: 10/5f3a99009ed5f2a5a67d06e2f298cc97bc86d462034173308156f15b43a6e850be8511dc204b9b94566305da2947f7d90289657237d210351a39059ff9d666cf languageName: node linkType: hard -"json-parse-even-better-errors@npm:^5.0.0": - version: 5.0.0 - resolution: "json-parse-even-better-errors@npm:5.0.0" - checksum: 10/b5aeaa65e072bc3bda2cb1da50bf1822814b4aa7c568e7c2bed25af89d730f113dcb74393da574c0a32e889eeba4a826db600b8a6ecef917c59c8c6b38f2efaa +"json-parse-even-better-errors@npm:^4.0.0": + version: 4.0.0 + resolution: "json-parse-even-better-errors@npm:4.0.0" + checksum: 10/da1ae7ef0cc9db02972a06a71322f26bdcda5d7f648c23b28ce7f158ba35707461bcbd91945d8aace10d8d79c383b896725c65ffa410242352692328aa9b5edf languageName: node linkType: hard @@ -7710,13 +6377,6 @@ __metadata: languageName: node linkType: hard -"json-schema-typed@npm:^8.0.2": - version: 8.0.2 - resolution: "json-schema-typed@npm:8.0.2" - checksum: 10/fa866d1fe91e3a94aa4fe007861475cd03dcaf47b719861cab171ef2f8598478007c634d29ae45de94ee34ddff4e13414c63ea5ff06c5b868b613142c699d511 - languageName: node - linkType: hard - "json5@npm:^2.1.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -7857,11 +6517,11 @@ __metadata: languageName: node linkType: hard -"less-loader@npm:12.3.2": - version: 12.3.2 - resolution: "less-loader@npm:12.3.2" +"less-loader@npm:12.3.0": + version: 12.3.0 + resolution: "less-loader@npm:12.3.0" peerDependencies: - "@rspack/core": 0.x || ^1.0.0 || ^2.0.0-0 + "@rspack/core": 0.x || 1.x less: ^3.5.0 || ^4.0.0 webpack: ^5.0.0 peerDependenciesMeta: @@ -7869,15 +6529,15 @@ __metadata: optional: true webpack: optional: true - checksum: 10/9018d0dae852d99370b7202b548b34334b1a069123bae3717b20143290f17fb639da4f6fd9894b659e100af603f03d1eb74922d6b761e8ba91d1f758d692459a + checksum: 10/6f2eb5602c6d2f2d40afe340567b510dcbb8125d0cbf5e2ab02ed5cff43b26fe228d7baa0d47a940d7bebb1727be331f41c31a8f4d6ef8678fc96cc8a1dd60ad languageName: node linkType: hard -"less@npm:4.6.4": - version: 4.6.4 - resolution: "less@npm:4.6.4" +"less@npm:4.3.0": + version: 4.3.0 + resolution: "less@npm:4.3.0" dependencies: - copy-anything: "npm:^3.0.5" + copy-anything: "npm:^2.0.1" errno: "npm:^0.1.1" graceful-fs: "npm:^4.1.2" image-size: "npm:~0.5.0" @@ -7886,6 +6546,7 @@ __metadata: needle: "npm:^3.1.0" parse-node-version: "npm:^1.0.1" source-map: "npm:~0.6.0" + tslib: "npm:^2.3.0" dependenciesMeta: errno: optional: true @@ -7903,7 +6564,7 @@ __metadata: optional: true bin: lessc: bin/lessc - checksum: 10/8463c56e3eeac147ce4da7a2e7178e685fe2f8579c165c79381a3a4f87d70555058c9d1ebf420e272e90f3ee9b488cb3d418ed0c7a5681d26d20c5c1a956ea50 + checksum: 10/893d0589394ff29ebb245104728906784da061e077c063b89f31b72f162e3724a40ccdf684758c154f777b26afb9a292e85ce371e74b8d2eb90e878e9f99c432 languageName: node linkType: hard @@ -7928,31 +6589,31 @@ __metadata: languageName: node linkType: hard -"listr2@npm:10.2.1": - version: 10.2.1 - resolution: "listr2@npm:10.2.1" +"listr2@npm:8.3.3": + version: 8.3.3 + resolution: "listr2@npm:8.3.3" dependencies: - cli-truncate: "npm:^5.2.0" - eventemitter3: "npm:^5.0.4" + cli-truncate: "npm:^4.0.0" + colorette: "npm:^2.0.20" + eventemitter3: "npm:^5.0.1" log-update: "npm:^6.1.0" rfdc: "npm:^1.4.1" - wrap-ansi: "npm:^10.0.0" - checksum: 10/19f6356e6e2b56d6d3be30e3cf3ac511a8c081b5dac75c3c1e26cf52e07a7c59b63c1380862f3df2807b2284f8fa00864527777b6fcdb07be4c3f116947dee0d + wrap-ansi: "npm:^9.0.0" + checksum: 10/92f1bb60e9a0f4fed9bff89fbab49d80fc889d29cf47c0a612f5a62a036dead49d3f697d3a79e36984768529bd3bfacb3343859eafceba179a8e66c034d99300 languageName: node linkType: hard -"lmdb@npm:3.5.4": - version: 3.5.4 - resolution: "lmdb@npm:3.5.4" - dependencies: - "@harperfast/extended-iterable": "npm:^1.0.3" - "@lmdb/lmdb-darwin-arm64": "npm:3.5.4" - "@lmdb/lmdb-darwin-x64": "npm:3.5.4" - "@lmdb/lmdb-linux-arm": "npm:3.5.4" - "@lmdb/lmdb-linux-arm64": "npm:3.5.4" - "@lmdb/lmdb-linux-x64": "npm:3.5.4" - "@lmdb/lmdb-win32-arm64": "npm:3.5.4" - "@lmdb/lmdb-win32-x64": "npm:3.5.4" +"lmdb@npm:3.3.0": + version: 3.3.0 + resolution: "lmdb@npm:3.3.0" + dependencies: + "@lmdb/lmdb-darwin-arm64": "npm:3.3.0" + "@lmdb/lmdb-darwin-x64": "npm:3.3.0" + "@lmdb/lmdb-linux-arm": "npm:3.3.0" + "@lmdb/lmdb-linux-arm64": "npm:3.3.0" + "@lmdb/lmdb-linux-x64": "npm:3.3.0" + "@lmdb/lmdb-win32-arm64": "npm:3.3.0" + "@lmdb/lmdb-win32-x64": "npm:3.3.0" msgpackr: "npm:^1.11.2" node-addon-api: "npm:^6.1.0" node-gyp: "npm:latest" @@ -7976,14 +6637,14 @@ __metadata: optional: true bin: download-lmdb-prebuilds: bin/download-prebuilds.js - checksum: 10/09426064df1dd0b9bf1244a58bb4e04524743d76ab351ea78ee88109737bacbdfb8800a7d363e03b2f3d82873dc2b4614905ce9bc68539ab16ee6d60c43a9f22 + checksum: 10/8740a449bc0ac146ba1f6b685c9d0b635f7b36f89a9222a1a7f3219929f8e34fb24f0e5cfe9ea2117a3a0fb06c3db2310855101a198c672d054fa959dd19abe7 languageName: node linkType: hard -"loader-runner@npm:^4.3.1": - version: 4.3.2 - resolution: "loader-runner@npm:4.3.2" - checksum: 10/fc0cf0026cdea7182720f58e8ff07869334bf299bd451d6192a8c2c4119ad493f1e0f5df099d031e81361f96196150d21047bf415edef4dc1e0fa02fa65ecced +"loader-runner@npm:^4.2.0": + version: 4.3.0 + resolution: "loader-runner@npm:4.3.0" + checksum: 10/555ae002869c1e8942a0efd29a99b50a0ce6c3296efea95caf48f00d7f6f7f659203ed6613688b6181aa81dc76de3e65ece43094c6dffef3127fe1a84d973cd3 languageName: node linkType: hard @@ -8028,13 +6689,13 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:^7.0.1": - version: 7.0.1 - resolution: "log-symbols@npm:7.0.1" +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" dependencies: - is-unicode-supported: "npm:^2.0.0" - yoctocolors: "npm:^2.1.1" - checksum: 10/0862313d84826b551582e39659b8586c56b65130c5f4f976420e2c23985228334f2a26fc4251ac22bf0a5b415d9430e86bf332557d934c10b036f9a549d63a09 + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10/fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 languageName: node linkType: hard @@ -8071,13 +6732,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.3.5 - resolution: "lru-cache@npm:11.3.5" - checksum: 10/3701b77e87765a3aea453402a7850bdbf7e02445210f35bd5ba1561f601f605f488bf9932be4a3851a6664073924f671a1ec99c4a1a98c457e0d126872a3e04f - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -8087,12 +6741,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" +"magic-string@npm:0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10/57d5691f41ed40d962d8bd300148114f53db67fadbff336207db10a99f2bdf4a1be9cac3a68ee85dba575912ee1d4402e4396408196ec2d3afd043b076156221 + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa languageName: node linkType: hard @@ -8115,7 +6769,7 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^14.0.3": +"make-fetch-happen@npm:^14.0.0, make-fetch-happen@npm:^14.0.1, make-fetch-happen@npm:^14.0.2, make-fetch-happen@npm:^14.0.3": version: 14.0.3 resolution: "make-fetch-happen@npm:14.0.3" dependencies: @@ -8134,26 +6788,6 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^15.0.0, make-fetch-happen@npm:^15.0.1, make-fetch-happen@npm:^15.0.4": - version: 15.0.5 - resolution: "make-fetch-happen@npm:15.0.5" - dependencies: - "@gar/promise-retry": "npm:^1.0.0" - "@npmcli/agent": "npm:^4.0.0" - "@npmcli/redact": "npm:^4.0.0" - cacache: "npm:^20.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^5.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^6.0.0" - ssri: "npm:^13.0.0" - checksum: 10/d2649effb06c00cb2b266057cb1c8c1e99cfc8d1378e7d9c26cc8f00be41bc63d59b77a5576ed28f8105acc57fb16220b64217f8d3a6a066a594c004aa163afa - languageName: node - linkType: hard - "math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" @@ -8168,37 +6802,6 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10/a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd - languageName: node - linkType: hard - -"memfs@npm:^4.56.10": - version: 4.57.2 - resolution: "memfs@npm:4.57.2" - dependencies: - "@jsonjoy.com/fs-core": "npm:4.57.2" - "@jsonjoy.com/fs-fsa": "npm:4.57.2" - "@jsonjoy.com/fs-node": "npm:4.57.2" - "@jsonjoy.com/fs-node-builtins": "npm:4.57.2" - "@jsonjoy.com/fs-node-to-fsa": "npm:4.57.2" - "@jsonjoy.com/fs-node-utils": "npm:4.57.2" - "@jsonjoy.com/fs-print": "npm:4.57.2" - "@jsonjoy.com/fs-snapshot": "npm:4.57.2" - "@jsonjoy.com/json-pack": "npm:^1.11.0" - "@jsonjoy.com/util": "npm:^1.9.0" - glob-to-regex.js: "npm:^1.0.1" - thingies: "npm:^2.5.0" - tree-dump: "npm:^1.0.3" - tslib: "npm:^2.0.0" - peerDependencies: - tslib: 2 - checksum: 10/872b08504889b616a2ec28655509632112d80f8f0fd6d8c23219f4f62b4ff7d6a890205e3127379d985f3bdcaf82909a9f2c3a17867495f98b4678bc2af8a458 - languageName: node - linkType: hard - "memfs@npm:^4.6.0": version: 4.17.0 resolution: "memfs@npm:4.17.0" @@ -8218,13 +6821,6 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-descriptors@npm:2.0.0" - checksum: 10/e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a - languageName: node - linkType: hard - "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -8232,6 +6828,13 @@ __metadata: languageName: node linkType: hard +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 + languageName: node + linkType: hard + "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -8263,14 +6866,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:^1.54.0": - version: 1.54.0 - resolution: "mime-db@npm:1.54.0" - checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 - languageName: node - linkType: hard - -"mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -8279,15 +6875,6 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.2": - version: 3.0.2 - resolution: "mime-types@npm:3.0.2" - dependencies: - mime-db: "npm:^1.54.0" - checksum: 10/9db0ad31f5eff10ee8f848130779b7f2d056ddfdb6bda696cb69be68d486d33a3457b4f3f9bdeb60d0736edb471bd5a7c0a384375c011c51c889fd0d5c3b893e - languageName: node - linkType: hard - "mime@npm:1.6.0, mime@npm:^1.4.1": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -8306,6 +6893,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10/d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + "mimic-function@npm:^5.0.0": version: 5.0.1 resolution: "mimic-function@npm:5.0.1" @@ -8313,15 +6907,15 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:2.10.2": - version: 2.10.2 - resolution: "mini-css-extract-plugin@npm:2.10.2" +"mini-css-extract-plugin@npm:2.9.2": + version: 2.9.2 + resolution: "mini-css-extract-plugin@npm:2.9.2" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/d2b01f25e229d04263274fceccfcb3ec0937a448859e4cb65e32652e26dde46ae6ecf7d15a52a4bb700bea6e47675db88691d3abc418901ec8542e1cfdc62b85 + checksum: 10/db6ddb8ba56affa1a295b57857d66bad435d36e48e1f95c75d16fadd6c70e3ba33e8c4141c3fb0e22b4d875315b41c4f58550c6ac73b50bdbe429f768297e3ff languageName: node linkType: hard @@ -8332,15 +6926,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.3, minimatch@npm:^10.1.1, minimatch@npm:^10.2.2": - version: 10.2.5 - resolution: "minimatch@npm:10.2.5" - dependencies: - brace-expansion: "npm:^5.0.5" - checksum: 10/19e87a931aff60ee7b9d80f39f817b8bfc54f61f8356ee3549fbf636dbccacacfec8d803eac73293955c4527cd085247dfc064bce4a5e349f8f3b85e2bf5da0f - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -8350,7 +6935,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.0, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -8390,21 +6975,6 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^5.0.0": - version: 5.0.2 - resolution: "minipass-fetch@npm:5.0.2" - dependencies: - iconv-lite: "npm:^0.7.2" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^2.0.0" - minizlib: "npm:^3.0.1" - dependenciesMeta: - iconv-lite: - optional: true - checksum: 10/4f3f65ea5b20a3a287765ebf21cc73e62031f754944272df2a3039296cc75a8fc2dc50b8a3c4f39ce3ac6e5cc583e8dc664d12c6ab98e0883d263e49f344bc86 - languageName: node - linkType: hard - "minipass-flush@npm:^1.0.5": version: 1.0.5 resolution: "minipass-flush@npm:1.0.5" @@ -8432,15 +7002,6 @@ __metadata: languageName: node linkType: hard -"minipass-sized@npm:^2.0.0": - version: 2.0.0 - resolution: "minipass-sized@npm:2.0.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10/3b89adf64ca705662f77481e278eff5ec0a57aeffb5feba7cc8843722b1e7770efc880f2a17d1d4877b2d7bf227873cd46afb4da44c0fd18088b601ea50f96bb - languageName: node - linkType: hard - "minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" @@ -8450,6 +7011,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10/61682162d29f45d3152b78b08bab7fb32ca10899bc5991ffe98afc18c9e9543bd1e3be94f8b8373ba6262497db63607079dc242ea62e43e7b2270837b7347c93 + languageName: node + linkType: hard + "minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" @@ -8457,10 +7025,13 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^7.1.3": - version: 7.1.3 - resolution: "minipass@npm:7.1.3" - checksum: 10/175e4d5e20980c3cd316ae82d2c031c42f6c746467d8b1905b51060a0ba4461441a0c25bb67c025fd9617f9a3873e152c7b543c6b5ac83a1846be8ade80dffd6 +"minizlib@npm:^2.1.1": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10/ae0f45436fb51344dcb87938446a32fbebb540d0e191d63b35e1c773d47512e17307bf54aa88326cc6d176594d00e4423563a091f7266c2f9a6872cdc1e234d1 languageName: node linkType: hard @@ -8474,15 +7045,6 @@ __metadata: languageName: node linkType: hard -"minizlib@npm:^3.1.0": - version: 3.1.0 - resolution: "minizlib@npm:3.1.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10/f47365cc2cb7f078cbe7e046eb52655e2e7e97f8c0a9a674f4da60d94fb0624edfcec9b5db32e8ba5a99a5f036f595680ae6fe02a262beaa73026e505cc52f99 - languageName: node - linkType: hard - "mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" @@ -8494,6 +7056,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 + languageName: node + linkType: hard + "mkdirp@npm:^3.0.1": version: 3.0.1 resolution: "mkdirp@npm:3.0.1" @@ -8579,19 +7150,17 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "mute-stream@npm:3.0.0" - checksum: 10/bee5db5c996a4585dbffc49e51fea10f3582d7f65441db9bc63126f16269541713c6ccb5a6fe37e08f627967b6eb28dd6b35e54a8dce53cf3837d7e010917b43 +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 languageName: node linkType: hard -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" - bin: - nanoid: bin/nanoid.cjs - checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10/d2e4fd2f5aa342b89b98134a8d899d8ef9b0a6d69274c4af9df46faa2d97aeb1f2ce83d867880d6de63643c52386579b99139801e24e7526c3b9b0a6d1e18d6c languageName: node linkType: hard @@ -8662,6 +7231,13 @@ __metadata: languageName: node linkType: hard +"node-forge@npm:^1": + version: 1.3.1 + resolution: "node-forge@npm:1.3.1" + checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d + languageName: node + linkType: hard + "node-gyp-build-optional-packages@npm:5.2.2": version: 5.2.2 resolution: "node-gyp-build-optional-packages@npm:5.2.2" @@ -8675,27 +7251,7 @@ __metadata: languageName: node linkType: hard -"node-gyp@npm:^12.1.0": - version: 12.3.0 - resolution: "node-gyp@npm:12.3.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.5.4" - tinyglobby: "npm:^0.2.12" - undici: "npm:^6.25.0" - which: "npm:^6.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10/cd97bf17f0f3e6288c42cc23a6db8528a98e7530abdb72ab558272906d603362e4558069f99f8a5250bc78f65ff305b1438caca4f1b31c81904a8798c242603e - languageName: node - linkType: hard - -"node-gyp@npm:latest": +"node-gyp@npm:^11.0.0, node-gyp@npm:latest": version: 11.1.0 resolution: "node-gyp@npm:11.1.0" dependencies: @@ -8722,13 +7278,6 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.36": - version: 2.0.38 - resolution: "node-releases@npm:2.0.38" - checksum: 10/0e6bba9d7e75dddf7d45c099f202ac7cb0eaeb363c36a34880c219e1697cf7369b6548e48f538490b706501ff9aa017e102adf3362184b9b64786de8377e0501 - languageName: node - linkType: hard - "nopt@npm:^8.0.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -8740,17 +7289,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" - dependencies: - abbrev: "npm:^4.0.0" - bin: - nopt: bin/nopt.js - checksum: 10/56a1ccd2ad711fb5115918e2c96828703cddbe12ba2c3bd00591758f6fa30e6f47dd905c59dbfcf9b773f3a293b45996609fb6789ae29d6bfcc3cf3a6f7d9fda - languageName: node - linkType: hard - "normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" @@ -8758,82 +7296,88 @@ __metadata: languageName: node linkType: hard -"npm-bundled@npm:^5.0.0": - version: 5.0.0 - resolution: "npm-bundled@npm:5.0.0" +"normalize-range@npm:^0.1.2": + version: 0.1.2 + resolution: "normalize-range@npm:0.1.2" + checksum: 10/9b2f14f093593f367a7a0834267c24f3cb3e887a2d9809c77d8a7e5fd08738bcd15af46f0ab01cc3a3d660386f015816b5c922cea8bf2ee79777f40874063184 + languageName: node + linkType: hard + +"npm-bundled@npm:^4.0.0": + version: 4.0.0 + resolution: "npm-bundled@npm:4.0.0" dependencies: - npm-normalize-package-bin: "npm:^5.0.0" - checksum: 10/0fea07f61f9a1ceaddc3cf88bcc5844bef173518f03568151d59a02da8754367e5398ef729486bc15884681f485f78903093dc4237319fb817768dcd18cfb549 + npm-normalize-package-bin: "npm:^4.0.0" + checksum: 10/aae98992772af7528a2e10c8edb6996dcb2070759aba1fd96c3f0cdba9c666fa6ba45c319376babffa9e11b1dca14960de35ce98750021184cc3ca0a4d5f1af6 languageName: node linkType: hard -"npm-install-checks@npm:^8.0.0": - version: 8.0.0 - resolution: "npm-install-checks@npm:8.0.0" +"npm-install-checks@npm:^7.1.0": + version: 7.1.1 + resolution: "npm-install-checks@npm:7.1.1" dependencies: semver: "npm:^7.1.1" - checksum: 10/eb4df6c3270ce6efcebcbc1a02997b3b4bcfa906ac2129ccef80eeffcf062d2e6dcbed02327109296725c1eb138ad93973303e025d2e0115f718fa4c09ed013f + checksum: 10/b12a528ae2bd612a07db438b157ff105226165df58bc965937e7f312642448b589ead227dc1c83a080e46a68c25ea9b4560f100e4a8d34fa7d4c6fd4f5605a0e languageName: node linkType: hard -"npm-normalize-package-bin@npm:^5.0.0": - version: 5.0.0 - resolution: "npm-normalize-package-bin@npm:5.0.0" - checksum: 10/969bc042d7bb029b5da7eb733e7642b238e3cb071ad57b56a3f128069bc1a3cbc2a4f4af30ee75b11660c368d60b89811ecd1430cf2ea1a7ff36f30052a4aeda +"npm-normalize-package-bin@npm:^4.0.0": + version: 4.0.0 + resolution: "npm-normalize-package-bin@npm:4.0.0" + checksum: 10/e1a0971e5640bc116c5197f9707d86dc404b6d8e13da2c7ea82baa5583b8da279a3c8607234aa1d733c2baac3b3eba87b156f021f20ae183dc4806530e61675d languageName: node linkType: hard -"npm-package-arg@npm:13.0.2, npm-package-arg@npm:^13.0.0": - version: 13.0.2 - resolution: "npm-package-arg@npm:13.0.2" +"npm-package-arg@npm:12.0.2, npm-package-arg@npm:^12.0.0": + version: 12.0.2 + resolution: "npm-package-arg@npm:12.0.2" dependencies: - hosted-git-info: "npm:^9.0.0" - proc-log: "npm:^6.0.0" + hosted-git-info: "npm:^8.0.0" + proc-log: "npm:^5.0.0" semver: "npm:^7.3.5" - validate-npm-package-name: "npm:^7.0.0" - checksum: 10/810868f4b8c666fc1979f33c5b45606f541be97e82958af486e8d3f5ff2c91f96cea56f22c4665a92dc9a23698cf831cba2e09691387d473f910f9e6590638b3 + validate-npm-package-name: "npm:^6.0.0" + checksum: 10/f61dacb42c02dfa00f97d9fbd7f3696b8fe651cf7dd0d8f4e7766b5835290d0967c20ec148b31b10d7f2931108c507813d9d2624b279769b1b2e4a356914d561 languageName: node linkType: hard -"npm-packlist@npm:^10.0.1": - version: 10.0.4 - resolution: "npm-packlist@npm:10.0.4" +"npm-packlist@npm:^10.0.0": + version: 10.0.0 + resolution: "npm-packlist@npm:10.0.0" dependencies: - ignore-walk: "npm:^8.0.0" - proc-log: "npm:^6.0.0" - checksum: 10/b35b8da896b05e53d0a4fabc9c5521d603cb931d8ce3df2566c69354ee5652ca0c3c93413a56c956bef1675f6f11deb6015efca630251e537aec1f7fd47e2e53 + ignore-walk: "npm:^7.0.0" + checksum: 10/485de36f56911137eadbba639ecaf2b76e549f338bfca583298599049ad8c430a45a4c2c88b8afa41a36a09a99552bac078ffb80a4598955e7ee7576f8d88865 languageName: node linkType: hard -"npm-pick-manifest@npm:^11.0.1": - version: 11.0.3 - resolution: "npm-pick-manifest@npm:11.0.3" +"npm-pick-manifest@npm:10.0.0, npm-pick-manifest@npm:^10.0.0": + version: 10.0.0 + resolution: "npm-pick-manifest@npm:10.0.0" dependencies: - npm-install-checks: "npm:^8.0.0" - npm-normalize-package-bin: "npm:^5.0.0" - npm-package-arg: "npm:^13.0.0" + npm-install-checks: "npm:^7.1.0" + npm-normalize-package-bin: "npm:^4.0.0" + npm-package-arg: "npm:^12.0.0" semver: "npm:^7.3.5" - checksum: 10/189872190af34f7eccf3c586ad2e21e8c093f90a8f716db80887e8defa2bfb3ea917f61f339908ce0487a4cb1df40fe592aee3e8fe76a180a5b15a887850921a + checksum: 10/12439bb85d7399874b4f099c98966a875e46537c43ac7b9072804c46b7af8441e734068fff1ba23465832d08989d5f0ceeaa060c9a77761653decc2487460e09 languageName: node linkType: hard -"npm-registry-fetch@npm:^19.0.0": - version: 19.1.1 - resolution: "npm-registry-fetch@npm:19.1.1" +"npm-registry-fetch@npm:^18.0.0": + version: 18.0.2 + resolution: "npm-registry-fetch@npm:18.0.2" dependencies: - "@npmcli/redact": "npm:^4.0.0" + "@npmcli/redact": "npm:^3.0.0" jsonparse: "npm:^1.3.1" - make-fetch-happen: "npm:^15.0.0" + make-fetch-happen: "npm:^14.0.0" minipass: "npm:^7.0.2" - minipass-fetch: "npm:^5.0.0" + minipass-fetch: "npm:^4.0.0" minizlib: "npm:^3.0.1" - npm-package-arg: "npm:^13.0.0" - proc-log: "npm:^6.0.0" - checksum: 10/a3f4614a8421b40f72c71cdb97aca3b710a508c929a00b6f795020eaabef19dbe4a3f5043703aa54a1dd56b6d92bc1e764f2299d5a47d6e883942495db085a5e + npm-package-arg: "npm:^12.0.0" + proc-log: "npm:^5.0.0" + checksum: 10/9e1dc4153be6e5868a732cbb6711347e97db6cf75caff8b65a58ed743cd1a445d3f1f4332481974389e9f507a0629380e91ab698c5362ff856f5785748f01ecf languageName: node linkType: hard -"nth-check@npm:^2.1.1": +"nth-check@npm:^2.0.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" dependencies: @@ -8863,7 +7407,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -8881,14 +7425,14 @@ __metadata: languageName: node linkType: hard -"on-headers@npm:~1.1.0": - version: 1.1.0 - resolution: "on-headers@npm:1.1.0" - checksum: 10/98aa64629f986fb8cc4517dd8bede73c980e31208cba97f4442c330959f60ced3dc6214b83420491f5111fc7c4f4343abe2ea62c85f505cf041d67850f238776 +"on-headers@npm:~1.0.2": + version: 1.0.2 + resolution: "on-headers@npm:1.0.2" + checksum: 10/870766c16345855e2012e9422ba1ab110c7e44ad5891a67790f84610bd70a72b67fdd71baf497295f1d1bf38dd4c92248f825d48729c53c0eae5262fb69fa171 languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.4.0": +"once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -8897,6 +7441,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^5.1.0": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10/e9fd0695a01cf226652f0385bf16b7a24153dbbb2039f764c8ba6d2306a8506b0e4ce570de6ad99c7a6eb49520743afdb66edd95ee979c1a342554ed49a9aadd + languageName: node + linkType: hard + "onetime@npm:^7.0.0": version: 7.0.0 resolution: "onetime@npm:7.0.0" @@ -8906,17 +7459,15 @@ __metadata: languageName: node linkType: hard -"open@npm:11.0.0": - version: 11.0.0 - resolution: "open@npm:11.0.0" +"open@npm:10.1.2": + version: 10.1.2 + resolution: "open@npm:10.1.2" dependencies: - default-browser: "npm:^5.4.0" + default-browser: "npm:^5.2.1" define-lazy-prop: "npm:^3.0.0" - is-in-ssh: "npm:^1.0.0" is-inside-container: "npm:^1.0.0" - powershell-utils: "npm:^0.1.0" - wsl-utils: "npm:^0.3.0" - checksum: 10/d4572cd0c1f40fe1713edce9e3812367acbfaac0e909a68be0ee0b5acf4ee0a567d01d432dc2d708a55aab684002ab47fe13c679213fb749fe096b77a5d8e51b + is-wsl: "npm:^3.1.0" + checksum: 10/dc0496486fd79289844d8cac678402384488696db60ae5c5a175748cd728c381689cd937527762685dc27530408da0f0dac7653769f9730e773aa439d6674b98 languageName: node linkType: hard @@ -8932,19 +7483,20 @@ __metadata: languageName: node linkType: hard -"ora@npm:9.3.0": - version: 9.3.0 - resolution: "ora@npm:9.3.0" +"ora@npm:5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" dependencies: - chalk: "npm:^5.6.2" - cli-cursor: "npm:^5.0.0" - cli-spinners: "npm:^3.2.0" - is-interactive: "npm:^2.0.0" - is-unicode-supported: "npm:^2.1.0" - log-symbols: "npm:^7.0.1" - stdin-discarder: "npm:^0.3.1" - string-width: "npm:^8.1.0" - checksum: 10/2d02d6b80aad2cdec4dbad6e510ad4d7b8e804d6293ca90b40a6dde954ff6eed429f4260a3fe2e878fb7dc5c852a943add0172f3908b1a2daa82cece451151bd + bl: "npm:^4.1.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + is-unicode-supported: "npm:^0.1.0" + log-symbols: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10/8d071828f40090a8e1c6e8f350c6eb065808e9ab2b3e57fa37e0d5ae78cb46dac00117c8f12c3c8b8da2923454afbd8265e08c10b69881170c5b269f451e7fef languageName: node linkType: hard @@ -8955,6 +7507,13 @@ __metadata: languageName: node linkType: hard +"os-tmpdir@npm:~1.0.2": + version: 1.0.2 + resolution: "os-tmpdir@npm:1.0.2" + checksum: 10/5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -8998,30 +7557,30 @@ __metadata: languageName: node linkType: hard -"pacote@npm:21.5.0": - version: 21.5.0 - resolution: "pacote@npm:21.5.0" +"pacote@npm:21.0.0": + version: 21.0.0 + resolution: "pacote@npm:21.0.0" dependencies: - "@gar/promise-retry": "npm:^1.0.0" - "@npmcli/git": "npm:^7.0.0" - "@npmcli/installed-package-contents": "npm:^4.0.0" - "@npmcli/package-json": "npm:^7.0.0" - "@npmcli/promise-spawn": "npm:^9.0.0" - "@npmcli/run-script": "npm:^10.0.0" - cacache: "npm:^20.0.0" + "@npmcli/git": "npm:^6.0.0" + "@npmcli/installed-package-contents": "npm:^3.0.0" + "@npmcli/package-json": "npm:^6.0.0" + "@npmcli/promise-spawn": "npm:^8.0.0" + "@npmcli/run-script": "npm:^9.0.0" + cacache: "npm:^19.0.0" fs-minipass: "npm:^3.0.0" minipass: "npm:^7.0.2" - npm-package-arg: "npm:^13.0.0" - npm-packlist: "npm:^10.0.1" - npm-pick-manifest: "npm:^11.0.1" - npm-registry-fetch: "npm:^19.0.0" - proc-log: "npm:^6.0.0" - sigstore: "npm:^4.0.0" - ssri: "npm:^13.0.0" - tar: "npm:^7.4.3" + npm-package-arg: "npm:^12.0.0" + npm-packlist: "npm:^10.0.0" + npm-pick-manifest: "npm:^10.0.0" + npm-registry-fetch: "npm:^18.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + sigstore: "npm:^3.0.0" + ssri: "npm:^12.0.0" + tar: "npm:^6.1.11" bin: pacote: bin/index.js - checksum: 10/5d31a986728ce10dea688887d31b98eaa8f08be15b9458c6d69257c3f576771dfca56475a7c49251675fcb827dfc1647c1dd69b29e84b40dae35efd9ee257307 + checksum: 10/941a3907bab24cee44db271db6f68f97e1516e0ebf04ea09afd7469f55041c07417222ab2ae055f814dfd9f28e67ade2fb55d9b3020835d7b5c7a50afde7238b languageName: node linkType: hard @@ -9053,27 +7612,27 @@ __metadata: languageName: node linkType: hard -"parse5-html-rewriting-stream@npm:8.0.1": - version: 8.0.1 - resolution: "parse5-html-rewriting-stream@npm:8.0.1" +"parse5-html-rewriting-stream@npm:7.1.0": + version: 7.1.0 + resolution: "parse5-html-rewriting-stream@npm:7.1.0" dependencies: - entities: "npm:^8.0.0" - parse5: "npm:^8.0.0" - parse5-sax-parser: "npm:^8.0.0" - checksum: 10/483524e99f066ebf0f9865cb6ec13dd9a63a4ef69ef9b71615e9bfdaff87058f9a326695c5a4a521857d08e1e5a960bb46a5a68923475a53f8ee569de452fa8b + entities: "npm:^6.0.0" + parse5: "npm:^7.0.0" + parse5-sax-parser: "npm:^7.0.0" + checksum: 10/31ae0eaeb6cd1d5ea83d0bdd0ee9870a9694f9a21872331867d1ae2a3d25d4f20dd5d9c4f787750464a1595e44b9547f871744db12d0ee8d8efc0688a37d5b02 languageName: node linkType: hard -"parse5-sax-parser@npm:^8.0.0": - version: 8.0.0 - resolution: "parse5-sax-parser@npm:8.0.0" +"parse5-sax-parser@npm:^7.0.0": + version: 7.0.0 + resolution: "parse5-sax-parser@npm:7.0.0" dependencies: - parse5: "npm:^8.0.0" - checksum: 10/469c6f34944ac93f2642a70049f21c4fd4319a6e5ab4dd41d6dcdb6c04762cb53eda245494bae8ef42610507a138ddad965d12d94adeee28f9e784797a2f7e0e + parse5: "npm:^7.0.0" + checksum: 10/b3b6ea12a38b58871111f7e235c31f96776b382d0b066b6dee17f815a13754bb3248a9a76007168e2a30d66def14161efcffb0bc8d71848a2159e6974b949c00 languageName: node linkType: hard -"parse5@npm:^7.1.2": +"parse5@npm:^7.0.0, parse5@npm:^7.1.2": version: 7.2.1 resolution: "parse5@npm:7.2.1" dependencies: @@ -9082,16 +7641,7 @@ __metadata: languageName: node linkType: hard -"parse5@npm:^8.0.0": - version: 8.0.1 - resolution: "parse5@npm:8.0.1" - dependencies: - entities: "npm:^8.0.0" - checksum: 10/671dedfe7cbf4714414317bc8c6b2a14c61ef44f8fd90c983b5b1870653af5aa2e3b4e25e38e9538a7120ea2b688c50908830da2bd0930d8fd4bce34aed024eb - languageName: node - linkType: hard - -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 @@ -9136,27 +7686,10 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.2": - version: 2.0.2 - resolution: "path-scurry@npm:2.0.2" - dependencies: - lru-cache: "npm:^11.0.0" - minipass: "npm:^7.1.2" - checksum: 10/2b4257422bcb870a4c2d205b3acdbb213a72f5e2250f61c80f79c9d014d010f82bdf8584441612c8e1fa4eb098678f5704a66fa8377d72646bad4be38e57a2c3 - languageName: node - linkType: hard - -"path-to-regexp@npm:^8.0.0": - version: 8.4.2 - resolution: "path-to-regexp@npm:8.4.2" - checksum: 10/70fd2cbce0b962cbcf4d312af07818bfce2bae11c09cf3bd86be99c0e30168238a1a7b02b18b452e73f075897df04597d30d63e56da7be41eecfc37998693389 - languageName: node - linkType: hard - -"path-to-regexp@npm:~0.1.12": - version: 0.1.13 - resolution: "path-to-regexp@npm:0.1.13" - checksum: 10/f1e4bdedc4fd41a3b8dd76e8b2e1183105348c6b205badc072581ca63dc6aa7976a8a67feaffcf0e505f51ac12cb1a2de7f3fef3e9085b6849e76232d73ddcba +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e languageName: node linkType: hard @@ -9167,10 +7700,10 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:4.0.4, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": - version: 4.0.4 - resolution: "picomatch@npm:4.0.4" - checksum: 10/f6ef80a3590827ce20378ae110ac78209cc4f74d39236370f1780f957b7ee41c12acde0e4651b90f39983506fd2f5e449994716f516db2e9752924aff8de93ce +"picomatch@npm:4.0.2, picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 languageName: node linkType: hard @@ -9181,13 +7714,6 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 - languageName: node - linkType: hard - "pify@npm:^4.0.1": version: 4.0.1 resolution: "pify@npm:4.0.1" @@ -9195,48 +7721,27 @@ __metadata: languageName: node linkType: hard -"piscina@npm:5.1.4": - version: 5.1.4 - resolution: "piscina@npm:5.1.4" +"piscina@npm:5.0.0": + version: 5.0.0 + resolution: "piscina@npm:5.0.0" dependencies: - "@napi-rs/nice": "npm:^1.0.4" + "@napi-rs/nice": "npm:^1.0.1" dependenciesMeta: "@napi-rs/nice": optional: true - checksum: 10/9801dd4f006e80b97cb740cdea02204a686198382d6914d9784372c8c6ec707d683c8d477b8a9ee83c0f48e421ecb74cd7b1d63ca4df5a5dc68c7cad0c8cca98 - languageName: node - linkType: hard - -"pkce-challenge@npm:^5.0.0": - version: 5.0.1 - resolution: "pkce-challenge@npm:5.0.1" - checksum: 10/51d11f68d5a78617cfb2e9c2706dadcc2cbe55ffb55b21d42a6ed848ac5159db2657bf6c966a5a414119aa839ceb64240afea35e9e1c06946b57606ed0b43789 - languageName: node - linkType: hard - -"pkijs@npm:^3.3.3": - version: 3.4.0 - resolution: "pkijs@npm:3.4.0" - dependencies: - "@noble/hashes": "npm:1.4.0" - asn1js: "npm:^3.0.6" - bytestreamjs: "npm:^2.0.1" - pvtsutils: "npm:^1.3.6" - pvutils: "npm:^1.1.3" - tslib: "npm:^2.8.1" - checksum: 10/a937347584b27012919f69e4b3865b2fd09dced85a344f9a22bbf1376dd9e1534ccbe0bbdb997807b4990b07865c1ea028447d78b2c8a64436d4d393193a0777 + checksum: 10/b5dc82d311b216739b12e084dfb9a2f1352a01164687129f108ad01901135c4a0dc5a86204bb69e81d4df0699b6567158202ae8465c212186841f0ee05f596f1 languageName: node linkType: hard -"postcss-loader@npm:8.2.1": - version: 8.2.1 - resolution: "postcss-loader@npm:8.2.1" +"postcss-loader@npm:8.1.1": + version: 8.1.1 + resolution: "postcss-loader@npm:8.1.1" dependencies: cosmiconfig: "npm:^9.0.0" - jiti: "npm:^2.5.1" - semver: "npm:^7.6.2" + jiti: "npm:^1.20.0" + semver: "npm:^7.5.4" peerDependencies: - "@rspack/core": 0.x || ^1.0.0 || ^2.0.0-0 + "@rspack/core": 0.x || 1.x postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 peerDependenciesMeta: @@ -9244,7 +7749,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/e8fbae55198fc3baa859a1b504621fe01918510f6b21bd124f6d4467687e5efcbe2f5eaa530377c8fd68c8fda93823592e9d6f7295daa3a5ef4d124c012192e2 + checksum: 10/7ae38e635119a808ec05e25a5d1327afd40f5f07e1ae40827e4be5e9d1d0adf0e8e277252c13ddbc8909a1bc53fecb15741db340b98966c2bd9cab867cfe5f10 languageName: node linkType: hard @@ -9299,15 +7804,6 @@ __metadata: languageName: node linkType: hard -"postcss-safe-parser@npm:^7.0.1": - version: 7.0.1 - resolution: "postcss-safe-parser@npm:7.0.1" - peerDependencies: - postcss: ^8.4.31 - checksum: 10/285f30877f3ef5d43586432394ef4fcab904cd5bcfff5c26f586eb630fbee490abf2ac6d81e64fa212fb64d03630d12c2f3c5196f5637bec5ba3d043562ddf30 - languageName: node - linkType: hard - "postcss-selector-parser@npm:^7.0.0": version: 7.1.0 resolution: "postcss-selector-parser@npm:7.1.0" @@ -9325,18 +7821,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.5.10, postcss@npm:^8.4.40, postcss@npm:^8.5.6": - version: 8.5.10 - resolution: "postcss@npm:8.5.10" - dependencies: - nanoid: "npm:^3.3.11" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10/7eac6169e535b63c8412e94d4f6047fc23efa3e9dde804b541940043c831b25f1cd867d83cd2c4371ad2450c8abcb42c208aa25668c1f0f3650d7f72faf711a8 - languageName: node - linkType: hard - -"postcss@npm:^8.2.14, postcss@npm:^8.4.49": +"postcss@npm:8.5.3, postcss@npm:^8.2.14, postcss@npm:^8.4.33, postcss@npm:^8.4.49, postcss@npm:^8.5.3": version: 8.5.3 resolution: "postcss@npm:8.5.3" dependencies: @@ -9347,24 +7832,10 @@ __metadata: languageName: node linkType: hard -"powershell-utils@npm:^0.1.0": - version: 0.1.0 - resolution: "powershell-utils@npm:0.1.0" - checksum: 10/4cc0846bc903ef9c8ac8cc9d178185d5972160a6c8776d44cf4c27ce31c0b614fc7cd20a53e8fcaf7f5296cdb34087a5d4396bdd863492972c84f76f3cadef67 - languageName: node - linkType: hard - "proc-log@npm:^5.0.0": version: 5.0.0 resolution: "proc-log@npm:5.0.0" - checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c - languageName: node - linkType: hard - -"proc-log@npm:^6.0.0, proc-log@npm:^6.1.0": - version: 6.1.0 - resolution: "proc-log@npm:6.1.0" - checksum: 10/9033f30f168ed5a0991b773d0c50ff88384c4738e9a0a67d341de36bf7293771eed648ab6a0562f62276da12fde91f3bbfc75ffff6e71ad49aafd74fc646be66 + checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c languageName: node linkType: hard @@ -9385,7 +7856,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": +"proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -9409,22 +7880,6 @@ __metadata: languageName: node linkType: hard -"pvtsutils@npm:^1.3.6": - version: 1.3.6 - resolution: "pvtsutils@npm:1.3.6" - dependencies: - tslib: "npm:^2.8.1" - checksum: 10/d45b12f8526e13ecf15fe09b30cde65501f3300fd2a07c11b28a966d434d1f767c8a61597ecba2e19c7eb19ca0c740341a6babc67a4f741e08b1ef1095c71663 - languageName: node - linkType: hard - -"pvutils@npm:^1.1.3, pvutils@npm:^1.1.5": - version: 1.1.5 - resolution: "pvutils@npm:1.1.5" - checksum: 10/9a5a71603c72bf9ea3a4501e8251e3f7a56026ed059bf63a18bd9a30cac6c35cc8250b39eb6291c1cb204cdeb6660663ab9bb2c74e85a512919bb2d614e340ea - languageName: node - linkType: hard - "qjobs@npm:^1.2.0": version: 1.2.0 resolution: "qjobs@npm:1.2.0" @@ -9441,21 +7896,19 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:~6.15.1": - version: 6.15.1 - resolution: "qs@npm:6.15.1" - dependencies: - side-channel: "npm:^1.1.0" - checksum: 10/ec10b9957446b3f4a38000940f6374720b4e2985209b89df197066038c951472ea24cd98d6bc6df73a0cbec75bc056f638032e3fb447345017ff7e0f0a2693ac +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b languageName: node linkType: hard -"qs@npm:~6.14.0": - version: 6.14.2 - resolution: "qs@npm:6.14.2" +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" dependencies: - side-channel: "npm:^1.1.0" - checksum: 10/682933a85bb4b7bd0d66e13c0a40d9e612b5e4bcc2cb9238f711a9368cd22d91654097a74fff93551e58146db282c56ac094957dfdc60ce64ea72c3c9d7779ac + safe-buffer: "npm:^5.1.0" + checksum: 10/4efd1ad3d88db77c2d16588dc54c2b52fd2461e70fe5724611f38d283857094fe09040fa2c9776366803c3152cf133171b452ef717592b65631ce5dc3a2bdafc languageName: node linkType: hard @@ -9478,30 +7931,6 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:^3.0.0, raw-body@npm:^3.0.1": - version: 3.0.2 - resolution: "raw-body@npm:3.0.2" - dependencies: - bytes: "npm:~3.1.2" - http-errors: "npm:~2.0.1" - iconv-lite: "npm:~0.7.0" - unpipe: "npm:~1.0.0" - checksum: 10/4168c82157bd69175d5bd960e59b74e253e237b358213694946a427a6f750a18b8e150f036fed3421b3e83294b071a4e2bb01037a79ccacdac05360c63d3ebba - languageName: node - linkType: hard - -"raw-body@npm:~2.5.3": - version: 2.5.3 - resolution: "raw-body@npm:2.5.3" - dependencies: - bytes: "npm:~3.1.2" - http-errors: "npm:~2.0.1" - iconv-lite: "npm:~0.4.24" - unpipe: "npm:~1.0.0" - checksum: 10/f35759fe5a6548e7c529121ead1de4dd163f899749a5896c42e278479df2d9d7f98b5bb17312737c03617765e5a1433e586f717616e5cfbebc13b4738b820601 - languageName: node - linkType: hard - "readable-stream@npm:^2.0.1": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" @@ -9517,7 +7946,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.0.6": +"readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -9535,13 +7964,6 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:^5.0.0": - version: 5.0.0 - resolution: "readdirp@npm:5.0.0" - checksum: 10/a17a591b51d8b912083660df159e8bd17305dc1a9ef27c869c818bd95ff59e3a6496f97e91e724ef433e789d559d24e39496ea1698822eb5719606dc9c1a923d - languageName: node - linkType: hard - "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -9551,7 +7973,7 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:^0.2.0, reflect-metadata@npm:^0.2.2": +"reflect-metadata@npm:^0.2.0": version: 0.2.2 resolution: "reflect-metadata@npm:0.2.2" checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 @@ -9567,15 +7989,6 @@ __metadata: languageName: node linkType: hard -"regenerate-unicode-properties@npm:^10.2.2": - version: 10.2.2 - resolution: "regenerate-unicode-properties@npm:10.2.2" - dependencies: - regenerate: "npm:^1.4.2" - checksum: 10/5041ee31185c4700de9dd76783fab9def51c412751190d523d621db5b8e35a6c2d91f1642c12247e7d94f84b8ae388d044baac1e88fc2ba0ac215ca8dc7bed38 - languageName: node - linkType: hard - "regenerate@npm:^1.4.2": version: 1.4.2 resolution: "regenerate@npm:1.4.2" @@ -9604,20 +8017,6 @@ __metadata: languageName: node linkType: hard -"regexpu-core@npm:^6.3.1": - version: 6.4.0 - resolution: "regexpu-core@npm:6.4.0" - dependencies: - regenerate: "npm:^1.4.2" - regenerate-unicode-properties: "npm:^10.2.2" - regjsgen: "npm:^0.8.0" - regjsparser: "npm:^0.13.0" - unicode-match-property-ecmascript: "npm:^2.0.0" - unicode-match-property-value-ecmascript: "npm:^2.2.1" - checksum: 10/bf5f85a502a17f127a1f922270e2ecc1f0dd071ff76a3ec9afcd6b1c2bf7eae1486d1e3b1a6d621aee8960c8b15139e6b5058a84a68e518e1a92b52e9322faf9 - languageName: node - linkType: hard - "regjsgen@npm:^0.8.0": version: 0.8.0 resolution: "regjsgen@npm:0.8.0" @@ -9636,17 +8035,6 @@ __metadata: languageName: node linkType: hard -"regjsparser@npm:^0.13.0": - version: 0.13.1 - resolution: "regjsparser@npm:0.13.1" - dependencies: - jsesc: "npm:~3.1.0" - bin: - regjsparser: bin/parser - checksum: 10/3383e9dab8bc8cd09efcd9538191b1e194b1921438ca69fce833d1a447d0625635229464cbc6cb03f33e5d342f2d343e2738fdac9132e2470bca621e480c02ec - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -9688,31 +8076,39 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.11": - version: 1.22.12 - resolution: "resolve@npm:1.22.12" +"resolve@npm:1.22.10, resolve@npm:^1.14.2": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" dependencies: - es-errors: "npm:^1.3.0" - is-core-module: "npm:^2.16.1" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/1d2a081e4b7198e2a70abd7bbbf8aea5380c2d074b6c870035aab50ebfb7312b6492b3588e752faef83a75147862a3d3e09b222bc9afd536804181fd3a515ef9 + checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181 languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.22.11#optional!builtin": - version: 1.22.12 - resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" +"resolve@patch:resolve@npm%3A1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: - es-errors: "npm:^1.3.0" - is-core-module: "npm:^2.16.1" + is-core-module: "npm:^2.16.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10/f80ad2c2b6820331cbe079198a184ffce322cfeca140065118066276bc08b03d5fa2c1ce652aeb584ec74050d1f656f46f034cc0dd9300452c5ab7866907f8c0 + checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10/f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 languageName: node linkType: hard @@ -9740,6 +8136,13 @@ __metadata: languageName: node linkType: hard +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec + languageName: node + linkType: hard + "rfdc@npm:^1.3.0, rfdc@npm:^1.4.1": version: 1.4.1 resolution: "rfdc@npm:1.4.1" @@ -9769,36 +8172,31 @@ __metadata: languageName: node linkType: hard -"rollup@npm:4.60.2, rollup@npm:^4.43.0": - version: 4.60.2 - resolution: "rollup@npm:4.60.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.60.2" - "@rollup/rollup-android-arm64": "npm:4.60.2" - "@rollup/rollup-darwin-arm64": "npm:4.60.2" - "@rollup/rollup-darwin-x64": "npm:4.60.2" - "@rollup/rollup-freebsd-arm64": "npm:4.60.2" - "@rollup/rollup-freebsd-x64": "npm:4.60.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.60.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.60.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.60.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.60.2" - "@rollup/rollup-linux-loong64-gnu": "npm:4.60.2" - "@rollup/rollup-linux-loong64-musl": "npm:4.60.2" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.60.2" - "@rollup/rollup-linux-ppc64-musl": "npm:4.60.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.60.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.60.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.60.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.60.2" - "@rollup/rollup-linux-x64-musl": "npm:4.60.2" - "@rollup/rollup-openbsd-x64": "npm:4.60.2" - "@rollup/rollup-openharmony-arm64": "npm:4.60.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.60.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.60.2" - "@rollup/rollup-win32-x64-gnu": "npm:4.60.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.60.2" - "@types/estree": "npm:1.0.8" +"rollup@npm:4.40.2, rollup@npm:^4.34.9": + version: 4.40.2 + resolution: "rollup@npm:4.40.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.40.2" + "@rollup/rollup-android-arm64": "npm:4.40.2" + "@rollup/rollup-darwin-arm64": "npm:4.40.2" + "@rollup/rollup-darwin-x64": "npm:4.40.2" + "@rollup/rollup-freebsd-arm64": "npm:4.40.2" + "@rollup/rollup-freebsd-x64": "npm:4.40.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.40.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.40.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.40.2" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.40.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.40.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.40.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.40.2" + "@rollup/rollup-linux-x64-musl": "npm:4.40.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.40.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.40.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.40.2" + "@types/estree": "npm:1.0.7" fsevents: "npm:~2.3.2" dependenciesMeta: "@rollup/rollup-android-arm-eabi": @@ -9821,13 +8219,9 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loong64-gnu": + "@rollup/rollup-linux-loongarch64-gnu": optional: true - "@rollup/rollup-linux-loong64-musl": - optional: true - "@rollup/rollup-linux-ppc64-gnu": - optional: true - "@rollup/rollup-linux-ppc64-musl": + "@rollup/rollup-linux-powerpc64le-gnu": optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true @@ -9839,36 +8233,17 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true - "@rollup/rollup-openbsd-x64": - optional: true - "@rollup/rollup-openharmony-arm64": - optional: true "@rollup/rollup-win32-arm64-msvc": optional: true "@rollup/rollup-win32-ia32-msvc": optional: true - "@rollup/rollup-win32-x64-gnu": - optional: true "@rollup/rollup-win32-x64-msvc": optional: true fsevents: optional: true bin: rollup: dist/bin/rollup - checksum: 10/354041c7d7f745866cc001bb09d157ae8c0602e82311823e724aa85a45c16346a09bd6bced9f773b0a16aba66abaf45e2952ec643e694a4b30df6dc69daa47ce - languageName: node - linkType: hard - -"router@npm:^2.2.0": - version: 2.2.0 - resolution: "router@npm:2.2.0" - dependencies: - debug: "npm:^4.4.0" - depd: "npm:^2.0.0" - is-promise: "npm:^4.0.0" - parseurl: "npm:^1.3.3" - path-to-regexp: "npm:^8.0.0" - checksum: 10/8949bd1d3da5403cc024e2989fee58d7fda0f3ffe9f2dc5b8a192f295f400b3cde307b0b554f7d44851077640f36962ca469a766b3d57410d7d96245a7ba6c91 + checksum: 10/ab767c56e37410257864e051fccbdaf448ac7774129bf39295de716af816c49e0247e72749959969efbd892fc64e096880fa269764adf765579100e81abf5e7c languageName: node linkType: hard @@ -9879,6 +8254,15 @@ __metadata: languageName: node linkType: hard +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10/cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d + languageName: node + linkType: hard + "rxjs@npm:7.8.2, rxjs@npm:^7.5.5": version: 7.8.2 resolution: "rxjs@npm:7.8.2" @@ -9888,7 +8272,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 @@ -9920,13 +8304,13 @@ __metadata: languageName: node linkType: hard -"sass-loader@npm:16.0.7": - version: 16.0.7 - resolution: "sass-loader@npm:16.0.7" +"sass-loader@npm:16.0.5": + version: 16.0.5 + resolution: "sass-loader@npm:16.0.5" dependencies: neo-async: "npm:^2.6.2" peerDependencies: - "@rspack/core": 0.x || ^1.0.0 || ^2.0.0-0 + "@rspack/core": 0.x || 1.x node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 sass: ^1.3.0 sass-embedded: "*" @@ -9942,24 +8326,24 @@ __metadata: optional: true webpack: optional: true - checksum: 10/074be7eadbbb2e14f581b06e9f1ec010807d76bd5cc5cd9c6a120fa377f1b94a7f8dc4b0eea5b12cf34511e9b00fe056093b7da02e849d3f2513bfe21e9f660f + checksum: 10/978b553900427c3fc24ed16b8258829d6a851bc5b0ab226cf43143fc08a0386e8cd07db367104d190a6cf0945af071805f70773525a970673c5b61fda4b7a59e languageName: node linkType: hard -"sass@npm:1.99.0": - version: 1.99.0 - resolution: "sass@npm:1.99.0" +"sass@npm:1.88.0": + version: 1.88.0 + resolution: "sass@npm:1.88.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" - immutable: "npm:^5.1.5" + immutable: "npm:^5.0.2" source-map-js: "npm:>=0.6.2 <2.0.0" dependenciesMeta: "@parcel/watcher": optional: true bin: sass: sass.js - checksum: 10/93f9d5c3b3e4659fb68a8e90d9637818581b0152dfb543ac159d57bb1e384f6b2009b7ac35ef88883f441cddfabd248fdcbba3fe814f4f23e3bfe58c917787a6 + checksum: 10/dfe4aec19409d7eabca793bb5b464d81b636c853be33157b1d8121959be3f066f22ebe1d233d0eaaf29fac21cd746ba29acb50ed9bbf6e0a3b892c8e5ed0daa5 languageName: node linkType: hard @@ -9982,15 +8366,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.3.3": - version: 4.3.3 - resolution: "schema-utils@npm:4.3.3" +"schema-utils@npm:^4.3.2": + version: 4.3.2 + resolution: "schema-utils@npm:4.3.2" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/dba77a46ad7ff0c906f7f09a1a61109e6cb56388f15a68070b93c47a691f516c6a3eb454f81a8cceb0a0e55b87f8b05770a02bfb1f4e0a3143b5887488b2f900 + checksum: 10/02c32c34aae762d48468f98465a96a167fede637772871c7c7d8923671ddb9f20b2cc6f6e8448ae6bef5363e3597493c655212c8b06a4ee73aa099d9452fbd8b languageName: node linkType: hard @@ -10001,22 +8385,22 @@ __metadata: languageName: node linkType: hard -"selfsigned@npm:^5.5.0": - version: 5.5.0 - resolution: "selfsigned@npm:5.5.0" +"selfsigned@npm:^2.4.1": + version: 2.4.1 + resolution: "selfsigned@npm:2.4.1" dependencies: - "@peculiar/x509": "npm:^1.14.2" - pkijs: "npm:^3.3.3" - checksum: 10/fe9be2647507c3ee21dcaf5cab20e1ae4b8b84eac83d2fe4d82f9a3b6c70636f9aaeeba0089e3343dcb13fbb31ef70c2e72c41f2e2dcf38368040b49830c670e + "@types/node-forge": "npm:^1.3.0" + node-forge: "npm:^1" + checksum: 10/52536623f1cfdeb2f8b9198377f2ce7931c677ea69421238d1dc1ea2983bbe258e56c19e7d1af87035cad7270f19b7e996eaab1212e724d887722502f68e17f2 languageName: node linkType: hard -"semver@npm:7.7.4, semver@npm:^7.6.2, semver@npm:^7.6.3": - version: 7.7.4 - resolution: "semver@npm:7.7.4" +"semver@npm:7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" bin: semver: bin/semver.js - checksum: 10/26bdc6d58b29528f4142d29afb8526bc335f4fc04c4a10f2b98b217f277a031c66736bf82d3d3bb354a2f6a3ae50f18fd62b053c4ac3f294a3d10a61f5075b75 + checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda languageName: node linkType: hard @@ -10047,50 +8431,33 @@ __metadata: languageName: node linkType: hard -"send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.1 - resolution: "send@npm:1.2.1" - dependencies: - debug: "npm:^4.4.3" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.1" - mime-types: "npm:^3.0.2" - ms: "npm:^2.1.3" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.2" - checksum: 10/274f842d69ccfa49d4940a85598c6825da58dee6cb8ea33b08d5bd3988e6a82267c4d7c32b23d0e4706aad076ee95b1edfa13f859877db9b589829019397e355 - languageName: node - linkType: hard - -"send@npm:~0.19.0, send@npm:~0.19.1": - version: 0.19.2 - resolution: "send@npm:0.19.2" +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" dependencies: debug: "npm:2.6.9" depd: "npm:2.0.0" destroy: "npm:1.2.0" - encodeurl: "npm:~2.0.0" + encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - fresh: "npm:~0.5.2" - http-errors: "npm:~2.0.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" mime: "npm:1.6.0" ms: "npm:2.1.3" - on-finished: "npm:~2.4.1" + on-finished: "npm:2.4.1" range-parser: "npm:~1.2.1" - statuses: "npm:~2.0.2" - checksum: 10/e932a592f62c58560b608a402d52333a8ae98a5ada076feb5db1d03adaa77c3ca32a7befa1c4fd6dedc186e88f342725b0cb4b3d86835eaf834688b259bef18d + statuses: "npm:2.0.1" + checksum: 10/1f6064dea0ae4cbe4878437aedc9270c33f2a6650a77b56a16b62d057527f2766d96ee282997dd53ec0339082f2aad935bc7d989b46b48c82fc610800dc3a1d0 languageName: node linkType: hard -"serialize-javascript@npm:^7.0.3": - version: 7.0.5 - resolution: "serialize-javascript@npm:7.0.5" - checksum: 10/6237c76ef6df3d1ad61dd4a393b71ca758c7654f4d1cf77529e513134c0f0660302e03b7ec88a8f3a3daa79e1f93d6de8218ecbc45e073d7cc6b66284a1d3e83 +"serialize-javascript@npm:^6.0.2": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" + dependencies: + randombytes: "npm:^2.1.0" + checksum: 10/445a420a6fa2eaee4b70cbd884d538e259ab278200a2ededd73253ada17d5d48e91fb1f4cd224a236ab62ea7ba0a70c6af29fc93b4f3d3078bf7da1c031fde58 languageName: node linkType: hard @@ -10109,27 +8476,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:^2.2.0": - version: 2.2.1 - resolution: "serve-static@npm:2.2.1" - dependencies: - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - parseurl: "npm:^1.3.3" - send: "npm:^1.2.0" - checksum: 10/71500fe80cc7163fec04e4297de7591ad1cb682d137fc030e7a53e57040fda5187e8082a9c1b2ef37f1d3f9c27c9a94d4ba61806ebc28938ba4a7c8947c9f71e - languageName: node - linkType: hard - -"serve-static@npm:~1.16.2": - version: 1.16.3 - resolution: "serve-static@npm:1.16.3" +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" dependencies: encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" parseurl: "npm:~1.3.3" - send: "npm:~0.19.1" - checksum: 10/149d6718dd9e53166784d0a65535e21a7c01249d9c51f57224b786a7306354c6807e7811a9f6c143b45c863b1524721fca2f52b5c81a8b5194e3dde034a03b9c + send: "npm:0.19.0" + checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 languageName: node linkType: hard @@ -10140,7 +8495,7 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": +"setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e @@ -10214,7 +8569,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.0.6": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -10227,6 +8582,13 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^3.0.2": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + "signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" @@ -10234,17 +8596,27 @@ __metadata: languageName: node linkType: hard -"sigstore@npm:^4.0.0": - version: 4.1.0 - resolution: "sigstore@npm:4.1.0" +"sigstore@npm:^3.0.0": + version: 3.1.0 + resolution: "sigstore@npm:3.1.0" + dependencies: + "@sigstore/bundle": "npm:^3.1.0" + "@sigstore/core": "npm:^2.0.0" + "@sigstore/protobuf-specs": "npm:^0.4.0" + "@sigstore/sign": "npm:^3.1.0" + "@sigstore/tuf": "npm:^3.1.0" + "@sigstore/verify": "npm:^2.1.0" + checksum: 10/fc2a38d11bd0e02b5dc8271e906ba7ea1aaa3dc19010dc6d29602b900532fa16b132cd6c80ec1c294f201f81f1277fb351020d0c65b6a62968f229db0b6c5a4f + languageName: node + linkType: hard + +"slice-ansi@npm:^5.0.0": + version: 5.0.0 + resolution: "slice-ansi@npm:5.0.0" dependencies: - "@sigstore/bundle": "npm:^4.0.0" - "@sigstore/core": "npm:^3.1.0" - "@sigstore/protobuf-specs": "npm:^0.5.0" - "@sigstore/sign": "npm:^4.1.0" - "@sigstore/tuf": "npm:^4.0.1" - "@sigstore/verify": "npm:^3.1.0" - checksum: 10/7312eed22f82bebcd80a897a163e220bb1df2c084c308d17fb431ff03ef28cf20e3b17312fd8024793dcefa27e794c31174d604a28fc85672a9d6d7f34bbd4a6 + ansi-styles: "npm:^6.0.0" + is-fullwidth-code-point: "npm:^4.0.0" + checksum: 10/7e600a2a55e333a21ef5214b987c8358fe28bfb03c2867ff2cbf919d62143d1812ac27b4297a077fdaf27a03da3678e49551c93e35f9498a3d90221908a1180e languageName: node linkType: hard @@ -10258,16 +8630,6 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^8.0.0": - version: 8.0.0 - resolution: "slice-ansi@npm:8.0.0" - dependencies: - ansi-styles: "npm:^6.2.3" - is-fullwidth-code-point: "npm:^5.1.0" - checksum: 10/6a7e146852047e26dd5857b35c767e52906549c580cce0ad2287cc32f54f5a582494f674817fc9ac21b2e4ac1ddeaa85b3dee409782681b465330278890c73a8 - languageName: node - linkType: hard - "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -10378,10 +8740,20 @@ __metadata: languageName: node linkType: hard -"source-map@npm:0.7.6": - version: 0.7.6 - resolution: "source-map@npm:0.7.6" - checksum: 10/c8d2da7c57c14f3fd7568f764b39ad49bbf9dd7632b86df3542b31fed117d4af2fb74a4f886fc06baf7a510fee68e37998efc3080aacdac951c36211dc29a7a3 +"source-map@npm:0.7.4": + version: 0.7.4 + resolution: "source-map@npm:0.7.4" + checksum: 10/a0f7c9b797eda93139842fd28648e868a9a03ea0ad0d9fa6602a0c1f17b7fb6a7dcca00c144476cccaeaae5042e99a285723b1a201e844ad67221bf5d428f1dc + languageName: node + linkType: hard + +"spdx-correct@npm:^3.0.0": + version: 3.2.0 + resolution: "spdx-correct@npm:3.2.0" + dependencies: + spdx-expression-parse: "npm:^3.0.0" + spdx-license-ids: "npm:^3.0.0" + checksum: 10/cc2e4dbef822f6d12142116557d63f5facf3300e92a6bd24e907e4865e17b7e1abd0ee6b67f305cae6790fc2194175a24dc394bfcc01eea84e2bdad728e9ae9a languageName: node linkType: hard @@ -10392,13 +8764,13 @@ __metadata: languageName: node linkType: hard -"spdx-expression-parse@npm:^4.0.0": - version: 4.0.0 - resolution: "spdx-expression-parse@npm:4.0.0" +"spdx-expression-parse@npm:^3.0.0": + version: 3.0.1 + resolution: "spdx-expression-parse@npm:3.0.1" dependencies: spdx-exceptions: "npm:^2.1.0" spdx-license-ids: "npm:^3.0.0" - checksum: 10/936be681fbf5edeec3a79c023136479f70d6edb3fd3875089ac86cd324c6c8c81add47399edead296d1d0af17ae5ce88c7f88885eb150b62c2ff6e535841ca6a + checksum: 10/a1c6e104a2cbada7a593eaa9f430bd5e148ef5290d4c0409899855ce8b1c39652bcc88a725259491a82601159d6dc790bedefc9016c7472f7de8de7361f8ccde languageName: node linkType: hard @@ -10452,15 +8824,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^13.0.0": - version: 13.0.1 - resolution: "ssri@npm:13.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/ae560d0378d074006a71b06af71bfbe84a3fe1ac6e16c1f07575f69e670d40170507fe52b21bcc23399429bc6a15f4bc3ea8d9bc88e9dfd7e87de564e6da6a72 - languageName: node - linkType: hard - "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -10475,20 +8838,6 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.1, statuses@npm:~2.0.2": - version: 2.0.2 - resolution: "statuses@npm:2.0.2" - checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 - languageName: node - linkType: hard - -"stdin-discarder@npm:^0.3.1": - version: 0.3.2 - resolution: "stdin-discarder@npm:0.3.2" - checksum: 10/63c6912146efe079fd048ecc02e5c3bf5aaa4cb268ad4e365603d845444dd3048daa45868c2690c5fe2d020ba47273c8a20df684a8c424fb4bd7f359c795c2f5 - languageName: node - linkType: hard - "streamroller@npm:^3.1.5": version: 3.1.5 resolution: "streamroller@npm:3.1.5" @@ -10500,7 +8849,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -10522,7 +8871,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^7.0.0, string-width@npm:^7.2.0": +"string-width@npm:^7.0.0": version: 7.2.0 resolution: "string-width@npm:7.2.0" dependencies: @@ -10533,16 +8882,6 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^8.1.0, string-width@npm:^8.2.0": - version: 8.2.0 - resolution: "string-width@npm:8.2.0" - dependencies: - get-east-asian-width: "npm:^1.5.0" - strip-ansi: "npm:^7.1.2" - checksum: 10/c4f62877ec08fca155e84a260eb4f58f473cfe5169bd1c1e21ffb563d8e0b7f6d705cc3d250f2ed6bb4f30ee9732ad026f54afaac77aa487e3d1dc1b1969e51b - languageName: node - linkType: hard - "string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -10579,15 +8918,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.1.2": - version: 7.2.0 - resolution: "strip-ansi@npm:7.2.0" - dependencies: - ansi-regex: "npm:^6.2.2" - checksum: 10/96da3bc6d73cfba1218625a3d66cf7d37a69bf0920d8735b28f9eeaafcdb6c1fe8440e1ae9eb1ba0ca355dbe8702da872e105e2e939fa93e7851b3cb5dd7d316 - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -10613,17 +8943,24 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.2.1": +"tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a languageName: node linkType: hard -"tapable@npm:^2.3.0, tapable@npm:^2.3.3": - version: 2.3.3 - resolution: "tapable@npm:2.3.3" - checksum: 10/21fb64a7ae1a0e11d855a6c33a22ae5ecf7e2f23170c942da673b44bf4c3aae8aa52451ef2792d0ce36c7feca13dceafa4f135105d66fc06912632488c0913fd +"tar@npm:^6.1.11": + version: 6.2.1 + resolution: "tar@npm:6.2.1" + dependencies: + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0 languageName: node linkType: hard @@ -10641,26 +8978,14 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.5.4": - version: 7.5.13 - resolution: "tar@npm:7.5.13" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10/2bc2b6f0349038a6621dbba1c4522d45752d5071b2994692257113c2050cd23fafc30308f820e5f8ad6fda3f7d7f92adc9a432aa733daa04c42af2061c021c3f - languageName: node - linkType: hard - -"terser-webpack-plugin@npm:^5.3.17": - version: 5.5.0 - resolution: "terser-webpack-plugin@npm:5.5.0" +"terser-webpack-plugin@npm:^5.3.11": + version: 5.3.13 + resolution: "terser-webpack-plugin@npm:5.3.13" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" schema-utils: "npm:^4.3.0" + serialize-javascript: "npm:^6.0.2" terser: "npm:^5.31.1" peerDependencies: webpack: ^5.1.0 @@ -10671,21 +8996,21 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/b6628967580a1e700bf2709c1900132a7d26ce3594f1d896cef48ac32a958d9e6a90914ce2c001b4e566002d9cf8a80d52e125fe5134997dda31f856a80b7dad + checksum: 10/6313764800616248759eb3714f918d454892d487f152a7a87105821c2af4032061d886f3e5f834613818d8b65507e6f7f71d1bf85a74536bfe04f7d612838472 languageName: node linkType: hard -"terser@npm:5.46.1": - version: 5.46.1 - resolution: "terser@npm:5.46.1" +"terser@npm:5.39.1": + version: 5.39.1 + resolution: "terser@npm:5.39.1" dependencies: "@jridgewell/source-map": "npm:^0.3.3" - acorn: "npm:^8.15.0" + acorn: "npm:^8.8.2" commander: "npm:^2.20.0" source-map-support: "npm:~0.5.20" bin: terser: bin/terser - checksum: 10/16d21179905e549dae2560e107d069ba0fdb801c637bf5f07c2f30431e53b1641151d5e35915ca6578ac1d9763984095723034bf1a26740b183093f200293f86 + checksum: 10/f73303c5ac748d7b9678bdb5f9ef47a6f77d2e761e6f9b8ccf0b97da472f97c3b4e98216b150f234fecfebd3f7b539384872320dd6e55a9ce6d28d57165b2d99 languageName: node linkType: hard @@ -10712,15 +9037,6 @@ __metadata: languageName: node linkType: hard -"thingies@npm:^2.5.0": - version: 2.6.0 - resolution: "thingies@npm:2.6.0" - peerDependencies: - tslib: ^2 - checksum: 10/722ca22cb54b6071ca489731b092538448d7634dd6b17ec9b89e846bea40bf0111084bdda8403f0970d716f33703e188978596cce9cd331a93d5d37882b39d74 - languageName: node - linkType: hard - "thunky@npm:^1.0.2": version: 1.1.0 resolution: "thunky@npm:1.1.0" @@ -10728,13 +9044,13 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:0.2.16, tinyglobby@npm:^0.2.15": - version: 0.2.16 - resolution: "tinyglobby@npm:0.2.16" +"tinyglobby@npm:0.2.13, tinyglobby@npm:^0.2.13": + version: 0.2.13 + resolution: "tinyglobby@npm:0.2.13" dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.4" - checksum: 10/5c2c41b572ada38449e7c86a5fe034f204a1dbba577225a761a14f29f48dc3f2fc0d81a6c56fcc67c5a742cc3aa9fb5e2ca18dbf22b610b0bc0e549b34d5a0f8 + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/b04557ee58ad2be5f2d2cbb4b441476436c92bb45ba2e1fc464d686b793392b305ed0bcb8b877429e9b5036bdd46770c161a08384c0720b6682b7cd6ac80e403 languageName: node linkType: hard @@ -10748,6 +9064,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.0.33": + version: 0.0.33 + resolution: "tmp@npm:0.0.33" + dependencies: + os-tmpdir: "npm:~1.0.2" + checksum: 10/09c0abfd165cff29b32be42bc35e80b8c64727d97dedde6550022e88fa9fd39a084660415ed8e3ebaa2aca1ee142f86df8b31d4196d4f81c774a3a20fd4b6abf + languageName: node + linkType: hard + "tmp@npm:^0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" @@ -10764,7 +9089,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": +"toidentifier@npm:1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -10780,57 +9105,37 @@ __metadata: languageName: node linkType: hard -"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": - version: 1.1.0 - resolution: "tree-dump@npm:1.1.0" - peerDependencies: - tslib: 2 - checksum: 10/2c20118d2671996aa6f1ba1310cef1404fb525bde5d989ab542013f62b23a3633c0f0b32cbd516ee6205051ec21912b2470dabca006d19c9eba0740b567e2b60 +"tree-kill@npm:1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 languageName: node linkType: hard -"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.8.1": +"tslib@npm:2.8.1, tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.3.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 languageName: node linkType: hard -"tslib@npm:^1.9.3": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb - languageName: node - linkType: hard - -"tsyringe@npm:^4.10.0": - version: 4.10.0 - resolution: "tsyringe@npm:4.10.0" - dependencies: - tslib: "npm:^1.9.3" - checksum: 10/b42660dc112cee2db02b3d69f2ef6a6a9d185afd96b18d8f88e47c1e62be94b69a9f5a58fcfdb2a3fbb7c6c175b8162ea00f7db6499bf333ce945e570e31615c - languageName: node - linkType: hard - -"tuf-js@npm:^4.1.0": - version: 4.1.0 - resolution: "tuf-js@npm:4.1.0" +"tuf-js@npm:^3.0.1": + version: 3.0.1 + resolution: "tuf-js@npm:3.0.1" dependencies: - "@tufjs/models": "npm:4.1.0" - debug: "npm:^4.4.3" - make-fetch-happen: "npm:^15.0.1" - checksum: 10/ae6d3f3e5de940fd6b9faeab3964f9cbddd8885e6dc01d3db7bacdb009abf31a3fab2e10162fc527781a67b04fb957cda2b6aa0017ce49b695fd3c24167aed97 + "@tufjs/models": "npm:3.0.1" + debug: "npm:^4.3.6" + make-fetch-happen: "npm:^14.0.1" + checksum: 10/880219a55e9575fcbf2c15129284a13d093fb2a053874151df59b11511d1ba097df359deae7b4e731b16fc3abd8fceda5125a167ec0d16eb926a32b8f778faa8 languageName: node linkType: hard -"type-is@npm:^2.0.1": - version: 2.0.1 - resolution: "type-is@npm:2.0.1" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10/f4254070d9c3d83a6e573bcb95173008d73474ceadbbf620dd32d273940ca18734dff39c2b2480282df9afe5d1675ebed5499a00d791758748ea81f61a38961f languageName: node linkType: hard @@ -10851,23 +9156,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:6.0.2": - version: 6.0.2 - resolution: "typescript@npm:6.0.2" +"typescript@npm:5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/b9fc2fcee7ee8e5ca6f5138181964550531e18e2d20ecb71b802d4d495881e3444e0ef94cbb6e84eb2ba41e913f6feae3ca33cc722b32e6e6dfadb32d23b80e6 + checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A6.0.2#optional!builtin": - version: 6.0.2 - resolution: "typescript@patch:typescript@npm%3A6.0.2#optional!builtin::version=6.0.2&hash=5786d5" +"typescript@patch:typescript@npm%3A5.8.3#optional!builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/e5501dfcf8c5b6b9a61f6fa53decc40187cebe3a2b7b2bd51c615007ad4cf6d58298461fef63294f6be9d5f2f33019b2a2e2bf02d9cb7d7f6ec94372bf24ffa2 + checksum: 10/b9b1e73dabac5dc730c041325dbd9c99467c1b0d239f1b74ec3b90d831384af3e2ba973946232df670519147eb51a2c20f6f96163cea2b359f03de1e2091cc4f languageName: node linkType: hard @@ -10894,13 +9199,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^6.25.0": - version: 6.25.0 - resolution: "undici@npm:6.25.0" - checksum: 10/a475e45da3e1d1073283bb70531666f09a432eabff2b857bd7063d469a1ee1486192ff61dc0dadbb526673ce1120fee14d66a59b6b17d1e0bd3a4d5f0a52d0a6 - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.1 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.1" @@ -10925,13 +9223,6 @@ __metadata: languageName: node linkType: hard -"unicode-match-property-value-ecmascript@npm:^2.2.1": - version: 2.2.1 - resolution: "unicode-match-property-value-ecmascript@npm:2.2.1" - checksum: 10/a42bebebab4c82ea6d8363e487b1fb862f82d1b54af1b67eb3fef43672939b685780f092c4f235266b90225863afa1258d57e7be3578d8986a08d8fc309aabe1 - languageName: node - linkType: hard - "unicode-property-aliases-ecmascript@npm:^2.0.0": version: 2.1.0 resolution: "unicode-property-aliases-ecmascript@npm:2.1.0" @@ -10985,20 +9276,6 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.2.3": - version: 1.2.3 - resolution: "update-browserslist-db@npm:1.2.3" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10/059f774300efb4b084a49293143c511f3ae946d40397b5c30914e900cd5691a12b8e61b41dd54ed73d3b56c8204165a0333107dd784ccf8f8c81790bcc423175 - languageName: node - linkType: hard - "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -11022,40 +9299,50 @@ __metadata: languageName: node linkType: hard -"validate-npm-package-name@npm:^7.0.0": - version: 7.0.2 - resolution: "validate-npm-package-name@npm:7.0.2" - checksum: 10/2a9bdc6fd5e4284c8e02279446bfd3c38c0c01222555fd3b00b4765d9d47b217d4a200910be71b80b958f6baf40d2d32e812a8632633a2ce376a9b3b74811072 +"validate-npm-package-license@npm:^3.0.4": + version: 3.0.4 + resolution: "validate-npm-package-license@npm:3.0.4" + dependencies: + spdx-correct: "npm:^3.0.0" + spdx-expression-parse: "npm:^3.0.0" + checksum: 10/86242519b2538bb8aeb12330edebb61b4eb37fd35ef65220ab0b03a26c0592c1c8a7300d32da3cde5abd08d18d95e8dabfad684b5116336f6de9e6f207eec224 + languageName: node + linkType: hard + +"validate-npm-package-name@npm:^6.0.0": + version: 6.0.0 + resolution: "validate-npm-package-name@npm:6.0.0" + checksum: 10/4d018c4fa07f95534a5fea667adc653b1ef52f08bf56aff066c28394499d0a6949c0b00edbd7077c4dc1e041da9220af7c742ced67d7d2d6a1b07d10cbe91b29 languageName: node linkType: hard -"vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 languageName: node linkType: hard -"vite@npm:7.3.2": - version: 7.3.2 - resolution: "vite@npm:7.3.2" +"vite@npm:6.3.5": + version: 6.3.5 + resolution: "vite@npm:6.3.5" dependencies: - esbuild: "npm:^0.27.0" - fdir: "npm:^6.5.0" + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.4" fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" - tinyglobby: "npm:^0.2.15" + picomatch: "npm:^4.0.2" + postcss: "npm:^8.5.3" + rollup: "npm:^4.34.9" + tinyglobby: "npm:^0.2.13" peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 jiti: ">=1.21.0" - less: ^4.0.0 + less: "*" lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -11087,7 +9374,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/c5f7a9a60011c41c836cedf31c8ee7624102aff9b6a7f3aab2ff47639721bba0916f81994c3a3ea6577a16c4f0dfee1e7dbd244e0da8edd5954e3c6d48daaaa2 + checksum: 10/7bc3a1c5ef79413ad70daeeaf69b76cd1218d16aa18ed8ee08d74648ef17284f4a17c11f5cf42b573b6dc5e3d5f115110b67b1d23c2c699cfe404757764a634a languageName: node linkType: hard @@ -11098,13 +9385,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:2.5.1, watchpack@npm:^2.5.1": - version: 2.5.1 - resolution: "watchpack@npm:2.5.1" +"watchpack@npm:2.4.2, watchpack@npm:^2.4.1": + version: 2.4.2 + resolution: "watchpack@npm:2.4.2" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10/9c9cdd4a9f9ae146b10d15387f383f52589e4cc27b324da6be8e7e3e755255b062a69dd7f00eef2ce67b2c01e546aae353456e74f8c1350bba00462cc6375549 + checksum: 10/6bd4c051d9af189a6c781c3158dcb3069f432a0c144159eeb0a44117412105c61b2b683a5c9eebc4324625e0e9b76536387d0ba354594fa6cbbdf1ef60bee4c3 languageName: node linkType: hard @@ -11117,6 +9404,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: "npm:^1.0.3" + checksum: 10/182ebac8ca0b96845fae6ef44afd4619df6987fe5cf552fdee8396d3daa1fb9b8ec5c6c69855acb7b3c1231571393bd1f0a4cdc4028d421575348f64bb0a8817 + languageName: node + linkType: hard + "weak-lru-cache@npm:^1.2.2": version: 1.2.2 resolution: "weak-lru-cache@npm:1.2.2" @@ -11124,25 +9420,7 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:8.0.3": - version: 8.0.3 - resolution: "webpack-dev-middleware@npm:8.0.3" - dependencies: - memfs: "npm:^4.56.10" - mime-types: "npm:^3.0.2" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - schema-utils: "npm:^4.3.3" - peerDependencies: - webpack: ^5.101.0 - peerDependenciesMeta: - webpack: - optional: true - checksum: 10/32ac645eecad3f81c1d38cf6222591f11f3adf47c1ae92acfd5a241458706f4f026f78f0fcf7bf9a83ae93f34e32707a4a194e84caea3bc49580f9609c2f7f2d - languageName: node - linkType: hard - -"webpack-dev-middleware@npm:^7.4.2": +"webpack-dev-middleware@npm:7.4.2, webpack-dev-middleware@npm:^7.4.2": version: 7.4.2 resolution: "webpack-dev-middleware@npm:7.4.2" dependencies: @@ -11161,13 +9439,13 @@ __metadata: languageName: node linkType: hard -"webpack-dev-server@npm:5.2.3": - version: 5.2.3 - resolution: "webpack-dev-server@npm:5.2.3" +"webpack-dev-server@npm:5.2.1": + version: 5.2.1 + resolution: "webpack-dev-server@npm:5.2.1" dependencies: "@types/bonjour": "npm:^3.5.13" "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.25" + "@types/express": "npm:^4.17.21" "@types/express-serve-static-core": "npm:^4.17.21" "@types/serve-index": "npm:^1.9.4" "@types/serve-static": "npm:^1.15.5" @@ -11177,17 +9455,17 @@ __metadata: bonjour-service: "npm:^1.2.1" chokidar: "npm:^3.6.0" colorette: "npm:^2.0.10" - compression: "npm:^1.8.1" + compression: "npm:^1.7.4" connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.22.1" + express: "npm:^4.21.2" graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.9" + http-proxy-middleware: "npm:^2.0.7" ipaddr.js: "npm:^2.1.0" launch-editor: "npm:^2.6.1" open: "npm:^10.0.3" p-retry: "npm:^6.2.0" schema-utils: "npm:^4.2.0" - selfsigned: "npm:^5.5.0" + selfsigned: "npm:^2.4.1" serve-index: "npm:^1.9.1" sockjs: "npm:^0.3.24" spdy: "npm:^4.0.2" @@ -11202,7 +9480,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/6a3d55c5d84d10b5e23a0638e0031ef85b262947fdacc86d96b7392538ad5894ac14aebef1e2b877f8d27302c71247215b7aa6713e01b1c37d31342415b1a150 + checksum: 10/424edfe22b7bbe2301a38b8b519dfeb7643e0ca643be01af3fa48ec18512955c1952246741d7577bdb911ee09dcd6c521ade7d65e0059448ee69ab02bfac4624 languageName: node linkType: hard @@ -11217,20 +9495,13 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.0.0": +"webpack-sources@npm:^3.0.0, webpack-sources@npm:^3.2.3": version: 3.2.3 resolution: "webpack-sources@npm:3.2.3" checksum: 10/a661f41795d678b7526ae8a88cd1b3d8ce71a7d19b6503da8149b2e667fc7a12f9b899041c1665d39e38245ed3a59ab68de648ea31040c3829aa695a5a45211d languageName: node linkType: hard -"webpack-sources@npm:^3.3.4": - version: 3.4.0 - resolution: "webpack-sources@npm:3.4.0" - checksum: 10/5efe3eb7089143f80ac102709a8bc7ee6ad2aaee4cbb9bbaff0cebde72b6703b0574d0c8ce4df00b36c16355b5731278bf050a1406b8a0874c23806dc182f1d3 - languageName: node - linkType: hard - "webpack-subresource-integrity@npm:5.1.0": version: 5.1.0 resolution: "webpack-subresource-integrity@npm:5.1.0" @@ -11246,40 +9517,40 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.106.2": - version: 5.106.2 - resolution: "webpack@npm:5.106.2" +"webpack@npm:5.99.8": + version: 5.99.8 + resolution: "webpack@npm:5.99.8" dependencies: "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.8" + "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.16.0" - acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.28.1" + acorn: "npm:^8.14.0" + browserslist: "npm:^4.24.0" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.20.0" - es-module-lexer: "npm:^2.0.0" + enhanced-resolve: "npm:^5.17.1" + es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.2.11" - loader-runner: "npm:^4.3.1" - mime-db: "npm:^1.54.0" + json-parse-even-better-errors: "npm:^2.3.1" + loader-runner: "npm:^4.2.0" + mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.3" - tapable: "npm:^2.3.0" - terser-webpack-plugin: "npm:^5.3.17" - watchpack: "npm:^2.5.1" - webpack-sources: "npm:^3.3.4" + schema-utils: "npm:^4.3.2" + tapable: "npm:^2.1.1" + terser-webpack-plugin: "npm:^5.3.11" + watchpack: "npm:^2.4.1" + webpack-sources: "npm:^3.2.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/524dcd7f07dfa993ab46c5ae2e302aeaa98bed760e40644f61544c1db67e5cd7be24016c8ee245895eda024020cfa58a953f1771a21028ac5d040adc92240e0f + checksum: 10/e66b9bcc0ae2ea7fd08b90a551ecf066bf71841923d744edc83713a7fdacd0c121a1f6236164d1d18fce6d44642f2960cee2a102e5445c2ef7634c457334c9ae languageName: node linkType: hard @@ -11334,17 +9605,6 @@ __metadata: languageName: node linkType: hard -"which@npm:^6.0.0": - version: 6.0.1 - resolution: "which@npm:6.0.1" - dependencies: - isexe: "npm:^4.0.0" - bin: - node-which: bin/which.js - checksum: 10/dbea77c7d3058bf6c78bf9659d2dce4d2b57d39a15b826b2af6ac2e5a219b99dc8a831b79fdbc453c0598adb4f3f84cf9c2491fd52beb9f5d2dececcad117f68 - languageName: node - linkType: hard - "wildcard@npm:^2.0.1": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -11363,14 +9623,14 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^10.0.0": - version: 10.0.0 - resolution: "wrap-ansi@npm:10.0.0" +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" dependencies: - ansi-styles: "npm:^6.2.3" - string-width: "npm:^8.2.0" - strip-ansi: "npm:^7.1.2" - checksum: 10/b9ac5290e2c5b88cba928b72fbc04e4bb7196cebde1522eeb82a56b1e32c2e706a20169d305c182fe7e4b31100cb05bb1d98dc2747a4a98700ac44303b3ac2ce + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/0d64f2d438e0b555e693b95aee7b2689a12c3be5ac458192a1ce28f542a6e9e59ddfecc37520910c2c88eb1f82a5411260566dba5064e8f9895e76e169e76187 languageName: node linkType: hard @@ -11433,16 +9693,6 @@ __metadata: languageName: node linkType: hard -"wsl-utils@npm:^0.3.0": - version: 0.3.1 - resolution: "wsl-utils@npm:0.3.1" - dependencies: - is-wsl: "npm:^3.1.0" - powershell-utils: "npm:^0.1.0" - checksum: 10/46800b92fa4974f2a846a93f0b8c409a455c35897d001a7599b5524766b603c8fb0945d2b21ad6ad27d4b0ae7e72ca2e58d832ccfcaabf659399921c6448b1d0 - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -11478,24 +9728,25 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^22.0.0": - version: 22.0.0 - resolution: "yargs-parser@npm:22.0.0" - checksum: 10/f13c42bad6ebed1a587a72f2db5694f5fa772bcaf409a701691d13cf74eb5adfcf61a2611de08807e319b829d3e5e6e1578b16ebe174cae8e8be3bf7b8e7a19e +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e languageName: node linkType: hard -"yargs@npm:18.0.0, yargs@npm:^18.0.0": - version: 18.0.0 - resolution: "yargs@npm:18.0.0" +"yargs@npm:17.7.2, yargs@npm:^17.2.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - cliui: "npm:^9.0.1" + cliui: "npm:^8.0.1" escalade: "npm:^3.1.1" get-caller-file: "npm:^2.0.5" - string-width: "npm:^7.2.0" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" y18n: "npm:^5.0.5" - yargs-parser: "npm:^22.0.0" - checksum: 10/5af36234871390386b31cac99f00e79fcbc2ead858a61b30a8ca381c5fde5df8af0b407c36b000d3f774bcbe4aec5833f2f1c915f6ddc49ce97b78176b651801 + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 languageName: node linkType: hard @@ -11518,17 +9769,17 @@ __metadata: version: 0.0.0-use.local resolution: "yarn-pnp-compat@workspace:." dependencies: - "@angular-devkit/build-angular": "npm:22.0.0-next.6" + "@angular-devkit/build-angular": "npm:20.0.0-rc.3" "@angular/cdk": "npm:next" - "@angular/cli": "npm:22.0.0-next.6" - "@angular/common": "npm:22.0.0-next.9" - "@angular/compiler": "npm:22.0.0-next.9" - "@angular/compiler-cli": "npm:22.0.0-next.9" - "@angular/core": "npm:22.0.0-next.9" - "@angular/forms": "npm:22.0.0-next.9" + "@angular/cli": "npm:20.0.0-rc.3" + "@angular/common": "npm:20.0.0-rc.2" + "@angular/compiler": "npm:20.0.0-rc.2" + "@angular/compiler-cli": "npm:20.0.0-rc.2" + "@angular/core": "npm:20.0.0-rc.2" + "@angular/forms": "npm:20.0.0-rc.2" "@angular/material": "npm:next" - "@angular/platform-browser": "npm:22.0.0-next.9" - "@angular/router": "npm:22.0.0-next.9" + "@angular/platform-browser": "npm:20.0.0-rc.2" + "@angular/router": "npm:20.0.0-rc.2" "@types/jasmine": "npm:~5.1.7" "@types/node": "npm:22.15.33" glob: "npm:^7.2.0" @@ -11540,7 +9791,7 @@ __metadata: karma-jasmine-html-reporter: "npm:~2.1.0" rxjs: "npm:^7.5.5" tslib: "npm:^2.3.0" - typescript: "npm:6.0.2" + typescript: "npm:5.8.3" zone.js: "npm:~0.15.0" languageName: unknown linkType: soft @@ -11552,26 +9803,10 @@ __metadata: languageName: node linkType: hard -"yoctocolors@npm:^2.1.1": +"yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 - resolution: "yoctocolors@npm:2.1.2" - checksum: 10/6ee42d665a4cc161c7de3f015b2a65d6c65d2808bfe3b99e228bd2b1b784ef1e54d1907415c025fc12b400f26f372bfc1b71966c6c738d998325ca422eb39363 - languageName: node - linkType: hard - -"zod-to-json-schema@npm:^3.25.1": - version: 3.25.2 - resolution: "zod-to-json-schema@npm:3.25.2" - peerDependencies: - zod: ^3.25.28 || ^4 - checksum: 10/7035328654113f1a0b8e4c2d34a06f918c93650ef8a50d4fb30ad8f22e47d5762c163af9c82494756b34776bae3c41c26cfc6945105b0eee7dceb528cc07e665 - languageName: node - linkType: hard - -"zod@npm:4.3.6, zod@npm:^3.25 || ^4.0": - version: 4.3.6 - resolution: "zod@npm:4.3.6" - checksum: 10/25fc0f62e01b557b4644bf0b393bbaf47542ab30877c37837ea8caf314a8713d220c7d7fe51f68ffa72f0e1018ddfa34d96f1973d23033f5a2a5a9b6b9d9da01 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10/d731e3ba776a0ee19021d909787942933a6c2eafb2bbe85541f0c59aa5c7d475ce86fcb860d5803105e32244c3dd5ba875b87c4c6bf2d6f297da416aa54e556f languageName: node linkType: hard diff --git a/package.json b/package.json index c68d9630a5c9..94e51a51c621 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "url": "https://github.com/angular/components.git" }, "license": "MIT", - "packageManager": "pnpm@11.13.1", + "packageManager": "pnpm@10.29.3", "engines": { "npm": "Please use pnpm instead of NPM to install dependencies", "yarn": "Please use pnpm instead of Yarn to install dependencies", - "pnpm": "11.13.1" + "pnpm": "10.29.3" }, "scripts": { "ng-dev": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only node_modules/@angular/ng-dev/bundles/cli.mjs", @@ -53,9 +53,8 @@ "ci-docs-monitor-test": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/docs-deploy/monitoring/ci-test.mts", "prepare": "husky" }, - "version": "22.1.0-next.4", + "version": "21.3.0-next.0", "dependencies": { - "@angular/compiler-cli": "catalog:", "@angular-devkit/core": "catalog:", "@angular-devkit/schematics": "catalog:", "@angular/common": "catalog:", @@ -63,8 +62,8 @@ "@angular/core": "catalog:", "@angular/forms": "catalog:", "@angular/platform-browser": "catalog:", - "@types/google.maps": "^3.65.1", - "@types/youtube": "0.3.0", + "@types/google.maps": "^3.54.10", + "@types/youtube": "0.1.2", "date-fns": "^4.1.0", "luxon": "^3.7.2", "moment": "^2.29.1", @@ -72,26 +71,26 @@ "rxjs-tslint-rules": "^4.34.8", "safevalues": "^1.2.0", "tslib": "^2.3.1", - "typescript": "6.0.2", "zone.js": "~0.16.0" }, "devDependencies": { + "@angular/compiler-cli": "catalog:", "@angular/localize": "catalog:", - "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#7a67d5221a9efec2b69b102759d04223a3fc8552", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#869bfda5998a1298e4eb55dbc97edc059625af5a", "@angular/platform-server": "catalog:", "@angular/router": "catalog:", - "@babel/core": "^8.0.1", - "@bazel/bazelisk": "1.28.1", + "@babel/core": "^7.16.12", + "@bazel/bazelisk": "1.26.0", "@bazel/buildifier": "8.2.1", "@bazel/ibazel": "^0.28.0", "@bazel/runfiles": "6.5.0", "@firebase/app-types": "^0.9.0", - "@material/material-color-utilities": "^0.4.0", + "@material/material-color-utilities": "^0.3.0", "@octokit/rest": "22.0.1", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.0", "@schematics/angular": "catalog:", - "@types/babel__core": "^7.20.5", + "@types/babel__core": "^7.1.18", "@types/fs-extra": "^11.0.0", "@types/jasmine": "^6.0.0", "@types/luxon": "^3.0.0", @@ -105,8 +104,8 @@ "chalk": "^5.4.1", "dgeni": "^0.4.14", "dgeni-packages": "^0.30.0", - "esbuild": "^0.28.0", - "firebase-tools": "15.23.0", + "esbuild": "^0.27.0", + "firebase-tools": "15.2.1", "fs-extra": "^11.0.0", "glob": "^13.0.0", "highlight.js": "^11.0.0", @@ -122,29 +121,88 @@ "karma-parallel": "^0.3.1", "karma-sourcemap-loader": "^0.4.0", "magic-string": "0.30.21", - "marked": "^18.0.0", + "marked": "^17.0.0", "minimatch": "^10.0.3", "parse5": "^8.0.0", "postcss": "^8.4.17", "postcss-scss": "^4.0.4", "prettier": "^3.5.3", "protractor": "^7.0.0", + "requirejs": "^2.3.6", "rollup": "^4.52.3", - "rollup-plugin-dts": "6.4.1", - "rollup-plugin-sourcemaps2": "0.5.8", - "sass": "^1.99.0", + "rollup-plugin-dts": "6.3.0", + "rollup-plugin-sourcemaps2": "0.5.4", + "sass": "^1.97.2", "selenium-webdriver": "^3.6.0", "semver": "^7.3.5", "shelljs": "^0.10.0", "slugify": "^1.6.6", "source-map-support": "^0.5.21", "stylelint": "^14.14.0", + "terser": "^5.10.0", "ts-node": "^10.9.1", "tsec": "0.2.9", + "tsickle": "0.46.3", "tslint": "^6.1.3", "tsutils": "^3.21.0", + "typescript": "5.9.2", "vrsource-tslint-rules": "6.0.0", + "yaml": "^2.8.1", "yargs": "^18.0.0", "zx": "^8.0.0" + }, + "pnpm": { + "overrides": { + "typescript": "5.9.2" + }, + "onlyBuiltDependencies": [], + "packageExtensions": { + "dgeni-packages": { + "dependencies": { + "clonedeep": "*" + } + }, + "grpc-gcp": { + "dependencies": { + "protobufjs": "7.4.0" + } + }, + "@angular-eslint/eslint-plugin-template@19.3.0": { + "peerDependencies": { + "@typescript-eslint/parser": "*", + "@angular-eslint/template-parser": "*" + } + }, + "@angular/common": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + }, + "@angular/forms": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + }, + "@angular/platform-browser": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + }, + "@angular/router": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + }, + "@angular/localize": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + }, + "@angular/platform-server": { + "dependencies": { + "@nginfra/angular-linking": "1.0.9" + } + } + } } } diff --git a/pkg-externals.bzl b/pkg-externals.bzl index 9c091fb4030b..1729c610c585 100644 --- a/pkg-externals.bzl +++ b/pkg-externals.bzl @@ -24,7 +24,6 @@ PKG_EXTERNALS = [ "@angular/core/rxjs-interop", "@angular/core/testing", "@angular/forms", - "@angular/forms/signals", "@angular/platform-browser", "@angular/platform-browser/animations", "@angular/platform-server", @@ -54,7 +53,6 @@ PKG_EXTERNALS = [ "selenium-webdriver", "safevalues", "safevalues/dom", - "youtube", ] # Creates externals for a given package and its entry-points. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16705efde1f9..d62f6e9e591d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,63 +7,61 @@ settings: catalogs: default: '@angular-devkit/build-angular': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 '@angular-devkit/core': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 '@angular-devkit/schematics': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 '@angular/cli': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 '@angular/common': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/compiler': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/compiler-cli': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/core': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/forms': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/localize': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/platform-browser': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/platform-browser-dynamic': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/platform-server': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/router': - specifier: 22.1.0-next.6 - version: 22.1.0-next.6 + specifier: 21.2.0-next.3 + version: 21.2.0-next.3 '@angular/ssr': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 '@schematics/angular': - specifier: 22.1.0-next.4 - version: 22.1.0-next.4 + specifier: 21.2.0-next.2 + version: 21.2.0-next.2 rxjs: specifier: ^6.6.7 version: 6.6.7 overrides: - typescript: 6.0.2 - protobufjs: 8.7.1 - '@nginfra/angular-linking>@babel/core': 8.0.1 + typescript: 5.9.2 -packageExtensionsChecksum: sha256-7cbDEhULaW/aaI9P05TJGFOe13X+o7KNiPsdlfS/GiQ= +packageExtensionsChecksum: sha256-aKJQSfkoOl79hd5DYaabzHnSDIJkX/dIHNav8Qv+Vb8= importers: @@ -71,37 +69,34 @@ importers: dependencies: '@angular-devkit/core': specifier: 'catalog:' - version: 22.1.0-next.4(chokidar@5.0.0) + version: 21.2.0-next.2(chokidar@5.0.0) '@angular-devkit/schematics': specifier: 'catalog:' - version: 22.1.0-next.4(chokidar@5.0.0) + version: 21.2.0-next.2(chokidar@5.0.0) '@angular/common': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) '@angular/compiler': specifier: 'catalog:' - version: 22.1.0-next.6 - '@angular/compiler-cli': - specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) + version: 21.2.0-next.3 '@angular/core': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) + version: 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) '@angular/forms': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@angular/platform-browser': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) '@types/google.maps': - specifier: ^3.65.1 - version: 3.65.2 + specifier: ^3.54.10 + version: 3.58.1 '@types/youtube': - specifier: 0.3.0 - version: 0.3.0 + specifier: 0.1.2 + version: 0.1.2 date-fns: specifier: ^4.1.0 - version: 4.4.0 + version: 4.1.0 luxon: specifier: ^3.7.2 version: 3.7.2 @@ -113,38 +108,38 @@ importers: version: 6.6.7 rxjs-tslint-rules: specifier: ^4.34.8 - version: 4.34.8(tslint@6.1.3(typescript@6.0.2))(typescript@6.0.2) + version: 4.34.8(tslint@6.1.3(typescript@5.9.2))(typescript@5.9.2) safevalues: specifier: ^1.2.0 version: 1.2.0 tslib: specifier: ^2.3.1 version: 2.8.1 - typescript: - specifier: 6.0.2 - version: 6.0.2 zone.js: specifier: ~0.16.0 - version: 0.16.2 + version: 0.16.0 devDependencies: + '@angular/compiler-cli': + specifier: 'catalog:' + version: 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) '@angular/localize': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6) + version: 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3) '@angular/ng-dev': - specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#7a67d5221a9efec2b69b102759d04223a3fc8552 - version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/7a67d5221a9efec2b69b102759d04223a3fc8552(@modelcontextprotocol/sdk@1.29.0) + specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#869bfda5998a1298e4eb55dbc97edc059625af5a + version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/869bfda5998a1298e4eb55dbc97edc059625af5a(@modelcontextprotocol/sdk@1.26.0) '@angular/platform-server': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@angular/router': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@babel/core': - specifier: ^8.0.1 - version: 8.0.1 + specifier: ^7.16.12 + version: 7.29.0 '@bazel/bazelisk': - specifier: 1.28.1 - version: 1.28.1 + specifier: 1.26.0 + version: 1.26.0 '@bazel/buildifier': specifier: 8.2.1 version: 8.2.1 @@ -156,24 +151,24 @@ importers: version: 6.5.0 '@firebase/app-types': specifier: ^0.9.0 - version: 0.9.5 + version: 0.9.3 '@material/material-color-utilities': - specifier: ^0.4.0 - version: 0.4.0 + specifier: ^0.3.0 + version: 0.3.0 '@octokit/rest': specifier: 22.0.1 version: 22.0.1 '@rollup/plugin-commonjs': specifier: ^29.0.0 - version: 29.0.3(rollup@4.62.2) + version: 29.0.0(rollup@4.57.1) '@rollup/plugin-node-resolve': specifier: ^16.0.0 - version: 16.0.3(rollup@4.62.2) + version: 16.0.3(rollup@4.57.1) '@schematics/angular': specifier: 'catalog:' - version: 22.1.0-next.4(chokidar@5.0.0) + version: 21.2.0-next.2(chokidar@5.0.0) '@types/babel__core': - specifier: ^7.20.5 + specifier: ^7.1.18 version: 7.20.5 '@types/fs-extra': specifier: ^11.0.0 @@ -183,10 +178,10 @@ importers: version: 6.0.0 '@types/luxon': specifier: ^3.0.0 - version: 3.7.2 + version: 3.7.1 '@types/node': specifier: ^22.14.1 - version: 22.20.1 + version: 22.19.11 '@types/selenium-webdriver': specifier: ^3.0.17 version: 3.0.26 @@ -201,10 +196,10 @@ importers: version: 17.0.35 autoprefixer: specifier: ^10.4.2 - version: 10.5.2(postcss@8.5.19) + version: 10.4.24(postcss@8.5.6) axe-core: specifier: ^4.10.3 - version: 4.12.1 + version: 4.11.1 chalk: specifier: ^5.4.1 version: 5.6.2 @@ -215,17 +210,17 @@ importers: specifier: ^0.30.0 version: 0.30.0(dgeni@0.4.14) esbuild: - specifier: ^0.28.0 - version: 0.28.1 + specifier: ^0.27.0 + version: 0.27.3 firebase-tools: - specifier: 15.23.0 - version: 15.23.0(@types/node@22.20.1)(bufferutil@4.1.0)(encoding@0.1.13) + specifier: 15.2.1 + version: 15.2.1(@types/node@22.19.11)(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.2) fs-extra: specifier: ^11.0.0 - version: 11.3.6 + version: 11.3.3 glob: specifier: ^13.0.0 - version: 13.0.6 + version: 13.0.3 highlight.js: specifier: ^11.0.0 version: 11.11.1 @@ -234,10 +229,10 @@ importers: version: 9.1.7 jasmine: specifier: ^6.0.0 - version: 6.3.0 + version: 6.0.0 jasmine-core: specifier: ^6.0.0 - version: 6.3.0 + version: 6.0.1 jasmine-reporters: specifier: ^2.5.2 version: 2.5.2 @@ -266,71 +261,86 @@ importers: specifier: 0.30.21 version: 0.30.21 marked: - specifier: ^18.0.0 - version: 18.0.6 + specifier: ^17.0.0 + version: 17.0.2 minimatch: specifier: ^10.0.3 - version: 10.2.5 + version: 10.2.0 parse5: specifier: ^8.0.0 - version: 8.0.1 + version: 8.0.0 postcss: specifier: ^8.4.17 - version: 8.5.19 + version: 8.5.6 postcss-scss: specifier: ^4.0.4 - version: 4.0.9(postcss@8.5.19) + version: 4.0.9(postcss@8.5.6) prettier: specifier: ^3.5.3 - version: 3.9.5 + version: 3.8.1 protractor: specifier: ^7.0.0 version: 7.0.0 + requirejs: + specifier: ^2.3.6 + version: 2.3.8 rollup: specifier: ^4.52.3 - version: 4.62.2 + version: 4.57.1 rollup-plugin-dts: - specifier: 6.4.1 - version: 6.4.1(rollup@4.62.2)(typescript@6.0.2) + specifier: 6.3.0 + version: 6.3.0(rollup@4.57.1)(typescript@5.9.2) rollup-plugin-sourcemaps2: - specifier: 0.5.8 - version: 0.5.8(@types/node@22.20.1)(rollup@4.62.2) + specifier: 0.5.4 + version: 0.5.4(@types/node@22.19.11)(rollup@4.57.1) sass: - specifier: ^1.99.0 - version: 1.101.0 + specifier: ^1.97.2 + version: 1.97.2 selenium-webdriver: specifier: ^3.6.0 version: 3.6.0 semver: specifier: ^7.3.5 - version: 7.8.5 + version: 7.7.4 shelljs: specifier: ^0.10.0 version: 0.10.0 slugify: specifier: ^1.6.6 - version: 1.6.9 + version: 1.6.6 source-map-support: specifier: ^0.5.21 version: 0.5.21 stylelint: specifier: ^14.14.0 version: 14.16.1 + terser: + specifier: ^5.10.0 + version: 5.46.0 ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@22.20.1)(typescript@6.0.2) + version: 10.9.2(@types/node@22.19.11)(typescript@5.9.2) tsec: specifier: 0.2.9 - version: 0.2.9(@bazel/bazelisk@1.28.1)(typescript@6.0.2) + version: 0.2.9(@bazel/bazelisk@1.26.0)(typescript@5.9.2) + tsickle: + specifier: 0.46.3 + version: 0.46.3(typescript@5.9.2) tslint: specifier: ^6.1.3 - version: 6.1.3(typescript@6.0.2) + version: 6.1.3(typescript@5.9.2) tsutils: specifier: ^3.21.0 - version: 3.21.0(typescript@6.0.2) + version: 3.21.0(typescript@5.9.2) + typescript: + specifier: 5.9.2 + version: 5.9.2 vrsource-tslint-rules: specifier: 6.0.0 - version: 6.0.0(tslint@6.1.3(typescript@6.0.2))(typescript@6.0.2) + version: 6.0.0(tslint@6.1.3(typescript@5.9.2))(typescript@5.9.2) + yaml: + specifier: ^2.8.1 + version: 2.8.2 yargs: specifier: ^18.0.0 version: 18.0.0 @@ -351,25 +361,25 @@ importers: version: link:../src/cdk-experimental '@angular/common': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) '@angular/compiler': specifier: 'catalog:' - version: 22.1.0-next.6 + version: 21.2.0-next.3 '@angular/components-examples': specifier: workspace:* version: link:../src/components-examples '@angular/core': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) + version: 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) '@angular/forms': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@angular/google-maps': specifier: workspace:* version: link:../src/google-maps '@angular/localize': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6) + version: 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3) '@angular/material': specifier: workspace:* version: link:../src/material @@ -381,22 +391,22 @@ importers: version: link:../src/material-luxon-adapter '@angular/platform-browser': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) '@angular/platform-browser-dynamic': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))) '@angular/router': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + version: 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@angular/ssr': specifier: 'catalog:' - version: 22.1.0-next.4(ae2ab38a56a690c5ea7806ad50928984) + version: 21.2.0-next.2(732a4b7fff947bcf4560cdcda99ad9b0) '@angular/youtube-player': specifier: workspace:* version: link:../src/youtube-player '@stackblitz/sdk': specifier: ^1.11.0 - version: 1.11.1 + version: 1.11.0 luxon: specifier: ^3.7.2 version: 3.7.2 @@ -411,35 +421,35 @@ importers: version: 2.8.1 zone.js: specifier: ~0.16.0 - version: 0.16.2 + version: 0.16.0 devDependencies: '@angular-devkit/build-angular': specifier: 'catalog:' - version: 22.1.0-next.4(2a4d79353011b4a0fecac35f3da0f711) + version: 21.2.0-next.2(27ef118a8e7055bf6f347df217a8d356) '@angular/cli': specifier: 'catalog:' - version: 22.1.0-next.4(@types/node@22.20.1)(chokidar@5.0.0) + version: 21.2.0-next.2(@types/node@22.19.11)(chokidar@5.0.0)(hono@4.11.9) '@angular/compiler-cli': specifier: 'catalog:' - version: 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) + version: 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) '@bazel/bazelisk': specifier: ^1.12.1 - version: 1.28.1 + version: 1.26.0 '@types/jasmine': specifier: ^6.0.0 version: 6.0.0 '@types/node': specifier: ^22.14.1 - version: 22.20.1 + version: 22.19.11 '@types/shelljs': specifier: 0.10.0 version: 0.10.0 firebase-tools: - specifier: 15.23.0 - version: 15.23.0(@types/node@22.20.1)(bufferutil@4.1.0)(encoding@0.1.13) + specifier: 15.2.1 + version: 15.2.1(@types/node@22.19.11)(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.2) jasmine-core: specifier: ^6.0.0 - version: 6.3.0 + version: 6.0.1 jasmine-spec-reporter: specifier: 7.0.0 version: 7.0.0 @@ -463,7 +473,7 @@ importers: version: 2.9.1(bufferutil@4.1.0) lighthouse: specifier: ^13.0.0 - version: 13.4.0(bufferutil@4.1.0) + version: 13.0.3(bufferutil@4.1.0) lighthouse-logger: specifier: ~2.0.0 version: 2.0.2 @@ -474,20 +484,20 @@ importers: specifier: ^7.0.0 version: 7.0.0 puppeteer-core: - specifier: ^25.0.0 - version: 25.3.0(bufferutil@4.1.0) + specifier: ^24.6.1 + version: 24.37.3(bufferutil@4.1.0) sass: - specifier: 1.101.0 - version: 1.101.0 + specifier: 1.97.2 + version: 1.97.2 shelljs: specifier: ^0.10.0 version: 0.10.0 ts-node: specifier: 10.9.2 - version: 10.9.2(@types/node@22.20.1)(typescript@6.0.2) + version: 10.9.2(@types/node@22.19.11)(typescript@5.9.2) typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 5.9.2 + version: 5.9.2 integration: dependencies: @@ -527,7 +537,7 @@ importers: dependencies: parse5: specifier: ^8.0.0 - version: 8.0.1 + version: 8.0.0 tslib: specifier: ^2.3.0 version: 2.8.1 @@ -560,9 +570,6 @@ importers: '@angular/material': specifier: workspace:* version: link:../material - '@angular/material-date-fns-adapter': - specifier: workspace:* - version: link:../material-date-fns-adapter '@angular/material-experimental': specifier: workspace:* version: link:../material-experimental @@ -615,8 +622,8 @@ importers: src/google-maps: dependencies: '@types/google.maps': - specifier: ^3.65.1 - version: 3.65.2 + specifier: ^3.54.10 + version: 3.58.1 tslib: specifier: ^2.3.0 version: 2.8.1 @@ -704,8 +711,8 @@ importers: src/youtube-player: dependencies: '@types/youtube': - specifier: 0.3.0 - version: 0.3.0 + specifier: 0.1.2 + version: 0.1.2 safevalues: specifier: ^1.2.0 version: 1.2.0 @@ -715,100 +722,103 @@ importers: packages: - '@actions/core@3.0.1': - resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} + '@actions/core@3.0.0': + resolution: {integrity: sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==} '@actions/exec@3.0.0': resolution: {integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==} - '@actions/http-client@4.0.1': - resolution: {integrity: sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==} + '@actions/http-client@4.0.0': + resolution: {integrity: sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==} '@actions/io@3.0.2': resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} - '@algolia/abtesting@1.21.2': - resolution: {integrity: sha512-uXj0rgk30EpsKvOpuS+R+1XFDrnm56hED1Lz56e8uBkZdKCxw99LS2U8eXBqAHYU8kpkbsnV1GC8velBG070Hg==} + '@algolia/abtesting@1.13.0': + resolution: {integrity: sha512-Zrqam12iorp3FjiKMXSTpedGYznZ3hTEOAr2oCxI8tbF8bS1kQHClyDYNq/eV0ewMNLyFkgZVWjaS+8spsOYiQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-abtesting@5.55.2': - resolution: {integrity: sha512-y7Epol8HcjlBxKXHhyhfFPFhm78B3P6x9cCbCyGTdxjsdVCptXCy5hpkZWxjGpnaLHvWsHS4QRF0TiBOLst2xg==} + '@algolia/client-abtesting@5.47.0': + resolution: {integrity: sha512-aOpsdlgS9xTEvz47+nXmw8m0NtUiQbvGWNuSEb7fA46iPL5FxOmOUZkh8PREBJpZ0/H8fclSc7BMJCVr+Dn72w==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.55.2': - resolution: {integrity: sha512-8Pxj2VVmpM2d+UZufnlTq7T1QIcYPVugLV5XC50PnHsV5uRM9CSoYkg2Y+CwqwRk2La0xK5QsfZ0obIU+9XftQ==} + '@algolia/client-analytics@5.47.0': + resolution: {integrity: sha512-EcF4w7IvIk1sowrO7Pdy4Ako7x/S8+nuCgdk6En+u5jsaNQM4rTT09zjBPA+WQphXkA2mLrsMwge96rf6i7Mow==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.55.2': - resolution: {integrity: sha512-9L4IpIYUqA63a7sw1trnHQGUvwiAjKz67nsgDnal98JGAc7wyposRb0Iag+eiMuyzFFaSHLe2/rGyIo+PafRBA==} + '@algolia/client-common@5.47.0': + resolution: {integrity: sha512-Wzg5Me2FqgRDj0lFuPWFK05UOWccSMsIBL2YqmTmaOzxVlLZ+oUqvKbsUSOE5ud8Fo1JU7JyiLmEXBtgDKzTwg==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.55.2': - resolution: {integrity: sha512-ZBm2ytY5EHFcj+kjNsXxMNO/TGlOHe2fBFXGKHJOM1bk1rAy4o2YI+d9oV/w/jrqx44pvJMJlc8X6vKnCuDgUQ==} + '@algolia/client-insights@5.47.0': + resolution: {integrity: sha512-Ci+cn/FDIsDxSKMRBEiyKrqybblbk8xugo6ujDN1GSTv9RIZxwxqZYuHfdLnLEwLlX7GB8pqVyqrUSlRnR+sJA==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.55.2': - resolution: {integrity: sha512-3FGVW/jDk7sdYwqa2NKnF/qXWcttc4bvGrwNbvqz3VoWSRv42CNvRk+3Y9QJFIUf1vY50hAuVWUoFKdyc8vaXA==} + '@algolia/client-personalization@5.47.0': + resolution: {integrity: sha512-gsLnHPZmWcX0T3IigkDL2imCNtsQ7dR5xfnwiFsb+uTHCuYQt+IwSNjsd8tok6HLGLzZrliSaXtB5mfGBtYZvQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.55.2': - resolution: {integrity: sha512-JsG8LovDAYul5t8e533tZ3O1uZILxso5zsTtB7ONc5RJ8ACdTxAAC/jaOnsBNYb+x+STP7fzx/Iro55v5DNgoQ==} + '@algolia/client-query-suggestions@5.47.0': + resolution: {integrity: sha512-PDOw0s8WSlR2fWFjPQldEpmm/gAoUgLigvC3k/jCSi/DzigdGX6RdC0Gh1RR1P8Cbk5KOWYDuL3TNzdYwkfDyA==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.55.2': - resolution: {integrity: sha512-5wDnoIfC75zJ2MSHv5SSzTlRL2z7jQMbqQ5jrzottuq2p3oBObv8pD/JpXWu8pRaimaxNr3/Bs/KZIGVXxJ7hg==} + '@algolia/client-search@5.47.0': + resolution: {integrity: sha512-b5hlU69CuhnS2Rqgsz7uSW0t4VqrLMLTPbUpEl0QVz56rsSwr1Sugyogrjb493sWDA+XU1FU5m9eB8uH7MoI0g==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.55.2': - resolution: {integrity: sha512-da+SC6ikpza98W7C5ChsKEQDvZc8PQLQ0sxmQ5yMRsHpdD3iPKnclJA6ViB5Nr5T9qOX+IDswC6AyqY4V3rtug==} + '@algolia/ingestion@1.47.0': + resolution: {integrity: sha512-WvwwXp5+LqIGISK3zHRApLT1xkuEk320/EGeD7uYy+K8WwDd5OjXnhjuXRhYr1685KnkvWkq1rQ/ihCJjOfHpQ==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.55.2': - resolution: {integrity: sha512-Y8kEcPqCiIEeaGv83l9RRA09mfYECqAJHNnOyEtZc9UirI6XBMUyFVss/sSeYUiV/Lf30hkbWcl00V1uXsf86Q==} + '@algolia/monitoring@1.47.0': + resolution: {integrity: sha512-j2EUFKAlzM0TE4GRfkDE3IDfkVeJdcbBANWzK16Tb3RHz87WuDfQ9oeEW6XiRE1/bEkq2xf4MvZesvSeQrZRDA==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.55.2': - resolution: {integrity: sha512-5zmobuCQqFZkx+84Nt+suL7vo6jTh2CfAs2ndDSeTS2QHvnzP8YEEGWtWftjyACI0cK/FuH8urWwCHP+d2j8TA==} + '@algolia/recommend@5.47.0': + resolution: {integrity: sha512-+kTSE4aQ1ARj2feXyN+DMq0CIDHJwZw1kpxIunedkmpWUg8k3TzFwWsMCzJVkF2nu1UcFbl7xsIURz3Q3XwOXA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.55.2': - resolution: {integrity: sha512-qnGUUuWG66dRMnr33owLsrYIh9fHVxtU4R2rd3SpneAHuoAUcGbDOWNrj05glVU6M8yOqo9gQ22K8zpz0I8Xpg==} + '@algolia/requester-browser-xhr@5.47.0': + resolution: {integrity: sha512-Ja+zPoeSA2SDowPwCNRbm5Q2mzDvVV8oqxCQ4m6SNmbKmPlCfe30zPfrt9ho3kBHnsg37pGucwOedRIOIklCHw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.55.2': - resolution: {integrity: sha512-lKZ5uhafMvR7dWCJEyuaeyZitid1I3ICx+k0vGf5x/ktdIQvc7bndCiOPpmIDqUmN26FE3jTehkAzSqee95G2Q==} + '@algolia/requester-fetch@5.47.0': + resolution: {integrity: sha512-N6nOvLbaR4Ge+oVm7T4W/ea1PqcSbsHR4O58FJ31XtZjFPtOyxmnhgCmGCzP9hsJI6+x0yxJjkW5BMK/XI8OvA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.55.2': - resolution: {integrity: sha512-Zc90xvKWUvxcNicvvTO9Pr/hT2TAnkixOIzJm/KMj5Ptm2pKjk71ngTsdkbRtJQvhZ2Kr9N1YdIjLrNHB5P2xw==} + '@algolia/requester-node-http@5.47.0': + resolution: {integrity: sha512-z1oyLq5/UVkohVXNDEY70mJbT/sv/t6HYtCvCwNrOri6pxBJDomP9R83KOlwcat+xqBQEdJHjbrPh36f1avmZA==} engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular-devkit/architect@0.2201.0-next.4': - resolution: {integrity: sha512-iCXgG0JsLO4cjq2bGrupBsNdmJGCQSe2MZ7lkNx6xcwZdu6bUNihk+77zXGKWgzjbUocvr4DuH+E2eNT6BxbcQ==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/architect@0.2102.0-next.2': + resolution: {integrity: sha512-NyTKJlLThhV/+t5japC3B9aB3+0039edqe9i1M76mPit7R2TRl2N4ChVUfvrsnkWMZPcuYuo51Fiqn4mfZDbuQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular-devkit/build-angular@22.1.0-next.4': - resolution: {integrity: sha512-U/lp+pnfnND0cTFl9ZXvdbWg3WAfS1kfmfTTkDUjeYDOT0t9NYVnlKX3FPsxvw4go5U8Aq4iOSNGrNZrX8Jmzw==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - deprecated: Angular's Webpack support is deprecated. Use the esbuild and Vite-based "@angular/build" package instead. - peerDependencies: - '@angular/compiler-cli': ^22.0.0 || ^22.1.0-next.0 - '@angular/core': ^22.0.0 || ^22.1.0-next.0 - '@angular/localize': ^22.0.0 || ^22.1.0-next.0 - '@angular/platform-browser': ^22.0.0 || ^22.1.0-next.0 - '@angular/platform-server': ^22.0.0 || ^22.1.0-next.0 - '@angular/service-worker': ^22.0.0 || ^22.1.0-next.0 - '@angular/ssr': ^22.1.0-next.4 + '@angular-devkit/build-angular@21.2.0-next.2': + resolution: {integrity: sha512-NEn3C5eRBSva8ELLtge8eYnT8KLWGLqxtFMwTpXmJctapOOmti6dLJNNZTwb+EgwWLHnJtToKy6PfzDhB/lO9w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler-cli': ^21.0.0 || ^21.2.0-next.0 + '@angular/core': ^21.0.0 || ^21.2.0-next.0 + '@angular/localize': ^21.0.0 || ^21.2.0-next.0 + '@angular/platform-browser': ^21.0.0 || ^21.2.0-next.0 + '@angular/platform-server': ^21.0.0 || ^21.2.0-next.0 + '@angular/service-worker': ^21.0.0 || ^21.2.0-next.0 + '@angular/ssr': ^21.2.0-next.2 + '@web/test-runner': ^0.20.0 browser-sync: ^3.0.2 + jest: ^30.2.0 + jest-environment-jsdom: ^30.2.0 karma: ^6.3.0 - ng-packagr: ^22.0.0 || ^22.1.0-next.0 + ng-packagr: ^21.0.0 || ^21.2.0-next.0 + protractor: ^7.0.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 - typescript: 6.0.2 + typescript: 5.9.2 peerDependenciesMeta: '@angular/core': optional: true @@ -822,56 +832,62 @@ packages: optional: true '@angular/ssr': optional: true + '@web/test-runner': + optional: true browser-sync: optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true karma: optional: true ng-packagr: optional: true + protractor: + optional: true tailwindcss: optional: true - '@angular-devkit/build-webpack@0.2201.0-next.4': - resolution: {integrity: sha512-5mPq+jtF0zSZZBZrvUej+8AeKFbJRpo6IjEmEV4yc+Idz+k0m09/9sTmENJq7Ns+yzHB9I+A9yl2KPnGkhsWvA==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/build-webpack@0.2102.0-next.2': + resolution: {integrity: sha512-wefj1JSICwhv7bT5OB+16EAWtE8IyfjZ2rue1AO70c+f4k10nkZuP5dOurQcMJOjCjPlju9HjxC+PqzPje9ttA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: webpack: ^5.30.0 webpack-dev-server: ^5.0.2 - '@angular-devkit/core@22.1.0-next.4': - resolution: {integrity: sha512-EsY0CF1KV2IR17+mJBlpdaMPRoS7Ab0z6AfdgIRDZ9OYknWzFiGP5a9ghwUzZi9u0KVqBybnBAEbRyKNEs6d/w==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/core@21.2.0-next.2': + resolution: {integrity: sha512-PSwga7qIg6OISYIH01GcYY1CxXgbqqRbw8b8XMM/b4DFWpYmfmxQPN6Gp5qu1qo9xRCdsnI1abS1t2gADIhYQw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^5.0.0 peerDependenciesMeta: chokidar: optional: true - '@angular-devkit/schematics@22.1.0-next.4': - resolution: {integrity: sha512-u2RK8HmF1Zlnp0IVJniTDnRilwEVqvhEpLL369Fm4TW0lVIj8TSf9sarE7VfZo6uQMom0APBjsYElRgmGd+6Dw==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - - '@angular/build@22.1.0-next.4': - resolution: {integrity: sha512-iHyIRRxwdzJVS3P9w5WqwwIouVOKd1KUszHY63DsZq0NxwRTqUWV2hjE2oMoPCetRPnhzxesNj3g+P4+TO4StQ==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - peerDependencies: - '@angular/compiler': ^22.0.0 || ^22.1.0-next.0 - '@angular/compiler-cli': ^22.0.0 || ^22.1.0-next.0 - '@angular/core': ^22.0.0 || ^22.1.0-next.0 - '@angular/localize': ^22.0.0 || ^22.1.0-next.0 - '@angular/platform-browser': ^22.0.0 || ^22.1.0-next.0 - '@angular/platform-server': ^22.0.0 || ^22.1.0-next.0 - '@angular/service-worker': ^22.0.0 || ^22.1.0-next.0 - '@angular/ssr': ^22.1.0-next.4 - istanbul-lib-instrument: ^6.0.0 + '@angular-devkit/schematics@21.2.0-next.2': + resolution: {integrity: sha512-yhrquiW2Q5WKKEYQwk7dj7XWWLc4Q/lQKsycdtSd5bhoNxWKeairlKKt3kxDFjUMjhxRE8kwUBg4N95FTWB8kA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular/build@21.2.0-next.2': + resolution: {integrity: sha512-NGbgWsX5fp4uBm85bDi20tvT8NteP/WB1WX3d0uthP25wdcXvIdxOIMQrJhLEwXsWlyAvDFI3vp6M/5EgzPiYw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler': ^21.0.0 || ^21.2.0-next.0 + '@angular/compiler-cli': ^21.0.0 || ^21.2.0-next.0 + '@angular/core': ^21.0.0 || ^21.2.0-next.0 + '@angular/localize': ^21.0.0 || ^21.2.0-next.0 + '@angular/platform-browser': ^21.0.0 || ^21.2.0-next.0 + '@angular/platform-server': ^21.0.0 || ^21.2.0-next.0 + '@angular/service-worker': ^21.0.0 || ^21.2.0-next.0 + '@angular/ssr': ^21.2.0-next.2 karma: ^6.4.0 less: ^4.2.0 - ng-packagr: ^22.0.0 || ^22.1.0-next.0 + ng-packagr: ^21.0.0 || ^21.2.0-next.0 postcss: ^8.4.0 - rollup: ^4.0.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 - typescript: 6.0.2 + typescript: 5.9.2 vitest: ^4.0.8 peerDependenciesMeta: '@angular/core': @@ -886,8 +902,6 @@ packages: optional: true '@angular/ssr': optional: true - istanbul-lib-instrument: - optional: true karma: optional: true less: @@ -896,45 +910,43 @@ packages: optional: true postcss: optional: true - rollup: - optional: true tailwindcss: optional: true vitest: optional: true - '@angular/cli@22.1.0-next.4': - resolution: {integrity: sha512-GPtdvl0P/htYAUPtLwM+24s1+zls0gka4DUrTLgGQWH4rWsV1Yl5s9d5QX4VynIkbYWD0/pEN1Dymny9d8RS/A==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/cli@21.2.0-next.2': + resolution: {integrity: sha512-UI9a2aYBCmHORS4Rc4xXcD4djjcsS1mwVtfrJtROPfZVXVgTF57t+4jOmWv8i9JmcPCb4yR7BYy6AL5+lDEFkA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular/common@22.1.0-next.6': - resolution: {integrity: sha512-KsDNNzQrZNJsbFss5GTgJsxEQ0os1/2Y0/2jUtTom7VIr2e+c/EFfwH+/7qfmVRyzcWS6pv6Lvafnh9MhYejZQ==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/common@21.2.0-next.3': + resolution: {integrity: sha512-7Mfsk/ahcKqvPFW1e9IMnfyvsyGKARIORdVxI8r/05N+Bt1aeW6iZbteEqM2cCEFZ4UqWjkaqdlBZndevqfqsA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 22.1.0-next.6 + '@angular/core': 21.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@22.1.0-next.6': - resolution: {integrity: sha512-+QAeP/KYo3Ro2HGnf3gMj/RoclVoM8voXmqiSrSU/72FZo/wI14xJLCMYLA+X+CfWdoMahDCue1tnDIlY0U75Q==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/compiler-cli@21.2.0-next.3': + resolution: {integrity: sha512-q3OUWuK/mspdxRfPQq2RPnGosq7yYzWzK+FikEYHv9I2pPTqt1cXWArWzknLouxs2zVaRhpaqXreCGtBeicYwg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 22.1.0-next.6 - typescript: 6.0.2 + '@angular/compiler': 21.2.0-next.3 + typescript: 5.9.2 peerDependenciesMeta: typescript: optional: true - '@angular/compiler@22.1.0-next.6': - resolution: {integrity: sha512-nwSMAs8DoYu9VtisLCjEPhiWxKyl13/fWu4Meeln65Zu7PE0xYP1SThntkhcaZ7jLbZEHyhmHf1J+0cNDEDhPw==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/compiler@21.2.0-next.3': + resolution: {integrity: sha512-vMNEt+qFt4+k7lGoalHqmxgB6XiEWUTG7UaT9BkcU5Ezs64RLfkLVSGd0MNAuE2RL1gOa0/wvIl11yv0pD2T7g==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/core@22.1.0-next.6': - resolution: {integrity: sha512-TzeC/N0HX/H4QQ9y2uXhIjJqK3g9HrSnkxCirsm43UTLf9W8p1rP4nlxvA4BE4HgfZL6MQnUcI/XTsIbMoOz8g==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/core@21.2.0-next.3': + resolution: {integrity: sha512-9cieyRQ3DIWANEQ1DfjOuZS8EdzcPzaikO1Vi81hNmsr7an8yok0oRXfonkkudFUtAE0Xl6B1wYyD4FZ3ig47g==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/compiler': 22.1.0-next.6 + '@angular/compiler': 21.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: @@ -943,75 +955,74 @@ packages: zone.js: optional: true - '@angular/forms@22.1.0-next.6': - resolution: {integrity: sha512-qOZobvrP7l6L6wIx8Hxy3VV9H+j50e9gZx/n0sOVMQNuheOc2/xJLHflFqwhEa5ULKw41qcrSuI4BOmMyuOmYw==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/forms@21.2.0-next.3': + resolution: {integrity: sha512-YHrLsKO1fozXw0jptI3kwCc5w13DRQzf/Q8ajmtu+o2oy1NjYuoPlRyTBuIufThYKgBqC2ZfWOCWkOa8gyyKQA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 - '@angular/platform-browser': 22.1.0-next.6 + '@angular/common': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 + '@angular/platform-browser': 21.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/localize@22.1.0-next.6': - resolution: {integrity: sha512-DUmCRxffaVEMeCOxCHcsZiJpybUjU7G5NyDrSKDHTyOJMxMzzrmVLGMAnSdPQn2nlFKMiLOm0olka1GxMmRelA==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/localize@21.2.0-next.3': + resolution: {integrity: sha512-348iYNZ5PrnjxiSVuPE9XomIWrZNYOPPDq/3VVVTl/f9G+iwmHGfCJ1GsGxwg31DRu3X6ZFKw4IBqUyfLJaJiQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 22.1.0-next.6 - '@angular/compiler-cli': 22.1.0-next.6 + '@angular/compiler': 21.2.0-next.3 + '@angular/compiler-cli': 21.2.0-next.3 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/7a67d5221a9efec2b69b102759d04223a3fc8552': - resolution: {gitHosted: true, integrity: sha512-DQnETQLANEERiT5UIcBj1D4S1WYlYVOuwtog4JpslthLq0JqSkKXCp7o+1roasNfBrFLaOYMD7wqZiRhsxbNSw==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/7a67d5221a9efec2b69b102759d04223a3fc8552} - version: 0.0.0-7365173626bd7f475d30ba010bfb0e485233b199 + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/869bfda5998a1298e4eb55dbc97edc059625af5a': + resolution: {tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/869bfda5998a1298e4eb55dbc97edc059625af5a} + version: 0.0.0-0edcf22f9ce677699274f2d399325f99d3d56e81 hasBin: true - '@angular/platform-browser-dynamic@22.1.0-next.6': - resolution: {integrity: sha512-Dbo+tA2ww+2Vol7X1ReTr+ut1z55YRCaPAWufd7aSuG+ZEObZgmSJuvTApvJbRWZsw/2vbi4QacAzjDuTYER7Q==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} - deprecated: '@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.' + '@angular/platform-browser-dynamic@21.2.0-next.3': + resolution: {integrity: sha512-ZuBl1S+sn8Fm8nvEK/ahXi+teT/taWCh4UNa6NrQImUt6BJcsK+2S/6wlYkntLndNHbTQAyVVVBcpmKWuUtOtA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.1.0-next.6 - '@angular/compiler': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 - '@angular/platform-browser': 22.1.0-next.6 + '@angular/common': 21.2.0-next.3 + '@angular/compiler': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 + '@angular/platform-browser': 21.2.0-next.3 - '@angular/platform-browser@22.1.0-next.6': - resolution: {integrity: sha512-QCSL84wfNH7wKGiieQ5hCrQH7ZAij0AglcKGhw7gRe9zIaw/d6FPV/AyJDPV7pDU71CnRK2rG42l0+7TLonRQQ==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/platform-browser@21.2.0-next.3': + resolution: {integrity: sha512-YsKkQaZ4jPs/xicQ9baJ0hlSLfKfsBsXrwNBw7XjvnyinU8tlB707KZ0eThyx9FSoLeBO2FgrWM75NTiEd2Rvg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 22.1.0-next.6 - '@angular/common': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 + '@angular/animations': 21.2.0-next.3 + '@angular/common': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/platform-server@22.1.0-next.6': - resolution: {integrity: sha512-TRKcCLtaY/LWrYS3DE871Po4axPxLG+hRicITbzWch7nhqtY9lnkTC51oPPtt7758AaNkm2JxZckggG1TAFtCw==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/platform-server@21.2.0-next.3': + resolution: {integrity: sha512-ETjC41XcL0m19hONZebrGNKqOTFqtuz2qP6I5g4JKS6jDJ5aLx9ERtcq+ALnuEmPICcR7rbGzMigM2WTDTdZ0A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.1.0-next.6 - '@angular/compiler': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 - '@angular/platform-browser': 22.1.0-next.6 + '@angular/common': 21.2.0-next.3 + '@angular/compiler': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 + '@angular/platform-browser': 21.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/router@22.1.0-next.6': - resolution: {integrity: sha512-W+Hcs9AnPXxQJ2dUQSGQ8b9VYqSp9jw/wiqR97JcNdPkDcjOV/jQhCTGt0SJQhOzjQeQJmrK5Pzj9Rli0HhGaA==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + '@angular/router@21.2.0-next.3': + resolution: {integrity: sha512-NHokfVBqk8Elei9AtWreWdrm6oThwCbJZEbi3MqKJBEKCo33BEtZuv3QrwrbpNb+Pnm5RJVYMK4Z0sXvt/qohg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 - '@angular/platform-browser': 22.1.0-next.6 + '@angular/common': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 + '@angular/platform-browser': 21.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/ssr@22.1.0-next.4': - resolution: {integrity: sha512-BBbnq8TgR0vpSyRNHOYnvzWTVEmmMiZCeveOfCO9DtEzpoSkZvOnxKo/KeZegC+6ib5bZe/43/MUEd/4QOOi1A==} + '@angular/ssr@21.2.0-next.2': + resolution: {integrity: sha512-KffNLgdclSqpzfY1Lqym25KwpJKBtlJW1GRuisq2OoHCJJq8CBfMFms+NzRmq7nuhMkWPzBtx+6TlMmyqCM3Jg==} peerDependencies: - '@angular/common': ^22.0.0 || ^22.1.0-next.0 - '@angular/core': ^22.0.0 || ^22.1.0-next.0 - '@angular/platform-server': ^22.0.0 || ^22.1.0-next.0 - '@angular/router': ^22.0.0 || ^22.1.0-next.0 + '@angular/common': ^21.0.0 || ^21.2.0-next.0 + '@angular/core': ^21.0.0 || ^21.2.0-next.0 + '@angular/platform-server': ^21.0.0 || ^21.2.0-next.0 + '@angular/router': ^21.0.0 || ^21.2.0-next.0 peerDependenciesMeta: '@angular/platform-server': optional: true @@ -1019,567 +1030,531 @@ packages: '@apidevtools/json-schema-ref-parser@9.1.2': resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} + '@apphosting/build@0.1.7': + resolution: {integrity: sha512-zNgQGiAWDOj6c+4ylv5ej3nLGXzMAVmzCGMqlbSarHe4bvBmZ2C5GfBRdJksedP7C9pqlwTWpxU5+GSzhJ+nKA==} + hasBin: true + '@apphosting/common@0.0.8': resolution: {integrity: sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==} - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/code-frame@8.0.0': - resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@apphosting/common@0.0.9': + resolution: {integrity: sha512-ZbPZDcVhEN+8m0sf90PmQN4xWaKmmySnBSKKPaIOD0JvcDsRr509WenFEFlojP++VSxwFZDGG/TYsHs1FMMqpw==} - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@8.0.0': - resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} - '@babel/core@8.0.1': - resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/generator@8.0.0': - resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/generator@7.29.0': + resolution: {integrity: sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==} + engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@8.0.0': - resolution: {integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@8.0.0': - resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@8.0.1': - resolution: {integrity: sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@8.0.1': - resolution: {integrity: sha512-PydTbcVTiIfVweHMeY1u3MslaD/ZzvnaTNhJp+7ghofelLWshF66Ckc/ZsjStfvRQIKQ4uVG0yEJucyDtyrWgw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@1.0.0': - resolution: {integrity: sha512-9jzVaTeZyXRDKTgUnNzcPQMO8y0ga3o+Z4fKjNet9Fcx7slgKa83qRbz0EwROSd6qO6CoEe/HQszqSPKb5lhkw==} - engines: {node: ^20.19.0 || >=22.12.0} + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0 + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@8.0.0': - resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-member-expression-to-functions@8.0.0': - resolution: {integrity: sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@8.0.0': - resolution: {integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@8.0.1': - resolution: {integrity: sha512-UgAhl1kqiW5ciE0yCXqqvnb4H2n3IELJ7lIIQRezwDPilPEZX5i+Rvbja9MFTkwUn2biEiSMeV31aUzR4Lwakw==} - engines: {node: ^22.18.0 || >=24.11.0} - peerDependencies: - '@babel/core': ^8.0.0 - - '@babel/helper-optimise-call-expression@8.0.0': - resolution: {integrity: sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@8.0.1': - resolution: {integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==} - engines: {node: ^22.18.0 || >=24.11.0} - peerDependencies: - '@babel/core': ^8.0.0 + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@8.0.1': - resolution: {integrity: sha512-baAKuLEMmu6BCSY3tuiU7qglM1qOZt6F1SrFScA241oNqksxkxfEZEKztlGRmoVns9AQ5UgArH7RsUEjxWnzgQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@8.0.1': - resolution: {integrity: sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/helper-skip-transparent-expression-wrappers@8.0.0': - resolution: {integrity: sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} '@babel/helper-split-export-declaration@7.24.7': resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0': - resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.4': - resolution: {integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@8.0.0': - resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-wrap-function@8.0.0': - resolution: {integrity: sha512-Qpm8+wi5xfDkBfollanwriCcKniFfBmMmaKB01GVM6VGzKXo1fdxosZp04qEr5HM+LKhwr3hG1yRy8+ORsficA==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@8.0.0': - resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.4': - resolution: {integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==} - engines: {node: ^22.18.0 || >=24.11.0} - hasBin: true - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@8.0.1': - resolution: {integrity: sha512-Ytgjjne4RnG3Oig7ik+NfY4ebRY30BPptVkkyu1f72eINJXRM3/bkU++tIc5aPvyLmo4KH20avq0xJ2o+9aEnw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@8.0.1': - resolution: {integrity: sha512-X7pAMBhuKluA7UfwZNvKN0XVVu/AGeo84Z75eJl85rcb8J2aBzLK92btahM1X5h0oi0QIrbe0qIMA/0+4Buk7w==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@8.0.1': - resolution: {integrity: sha512-DJviKTxYfH0hFwnMiW4dnPyMGzS3Hrr4zUfXl1zwQ0QiGlGlNYklLoPSYEQr8S7nau0/K7NdQjTh0qbYuyFjCA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@8.0.1': - resolution: {integrity: sha512-DmR/N+B9+4PbURFj4+zdnWj49/PFAnK2bn8+E4ZAmwn3J5QCxnbG7Ep6aRfz9M8Aw+rBro0kIJQycvzFpl4buQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@8.0.1': - resolution: {integrity: sha512-x8bi0LFVD2xkULjfNn+hCMg16yAFHAM9fS/ThSFeYBi+0MP9K6qcY2BZb4urUwC7PYtEy5wPe6TKjOEjXrCGFA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@8.0.1': - resolution: {integrity: sha512-P8+RN2n7ts2s1vnE+lXdHYf+dhnmcGSen/kWzBsVluT9Sey5AqmcRXYWlHqgQxaNlKTD5YMa1tf5z4d1v8W88w==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-arrow-functions@8.0.1': - resolution: {integrity: sha512-o/gr7kRlq3PKLLuYth4udOsrC7geBerti+QtwPeyxMOsEQO1d8kDHqk9r2PtMx2y9i8FG7tzyTerfv1yMLSMsQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@8.0.1': - resolution: {integrity: sha512-kqnSMF1YHBzuiQrl68675i5Ma1oljvo+SJsNEZFZVBu5BUrVIZm9KId3ui2PdtLK2sv2zM8sJnjPDfgLxQlEqQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@8.0.1': - resolution: {integrity: sha512-e1jmmEU4p2Lx64sA1+EF8e8/RxPuegzbXcEbmFp5alDyLE+f2ViUpZ77bRWMXzihTwgVVmn/TOpqDbAuS5g1Ew==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-transform-block-scoped-functions@8.0.1': - resolution: {integrity: sha512-0V97/gcf7LIgPieEiK1YT0eXa18XJFSLOTZjzEZhA9SJIqZhD/IwGUrCitBzXSmnGCP7hchwC6svHtJ/Eidcpg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@8.0.1': - resolution: {integrity: sha512-HxiQvKsSCs2jOmMhjDrooHaZYOy6W8bqwXp/zjdgPjsNrda6tK9/CH3a/cVIeg6ge3hSS02ALqvqgIo4rTsuSg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@8.0.1': - resolution: {integrity: sha512-tORnYiVhIHnKj90TgbSZXrO24f9oEpA6MgFxpIDSKKlHv7AzBIRhkMlYevanueLNYaQXqZWarfCgXM4bWTfNiw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@8.0.1': - resolution: {integrity: sha512-NEVK+L0Le8h8tJ+IK0CGS5y9Yi1ZHxLj6M5PeanhMFuq9aSo0XI+Wtmbuyop6fTNukOm7ORNntf/kwid891vqQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-classes@8.0.1': - resolution: {integrity: sha512-phwyCES8kIMAdVOFw25ztmgAvkl2G+TvUv7azUYyrlR1Qoo3eLJC/MU3MGUKFZ4BWtsJ1NTJM1lKRLzKbswg7w==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@8.0.1': - resolution: {integrity: sha512-i4l3OGLO8DUDcwdnyraOvILbhqdUf4QgfzhVxSOSzRy49XKXrY7pwaSg9gDSKmhZfNPrEMciBSJSciQh/CjB1A==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@8.0.1': - resolution: {integrity: sha512-RtR8uLDl0QcCmqMNIkM8gmDeYZ3rS0ZH+sa+I6sfc09yFoqfp9AEPgBstq9KyfVb0lFCVSRFfJXCI70FIl5ccw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.12.0 - '@babel/plugin-transform-dotall-regex@8.0.1': - resolution: {integrity: sha512-czOUoSaZljJ92yu+bYlXqb/UBN8K9daNCob/B6/7nthSvfGP6YhCnfqD64XWfyb2dN4ypxALNplApoJrsMd4fw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@8.0.1': - resolution: {integrity: sha512-kNnVLkxFUEcTtCyB5PFVQ5Xoy88Bk1lU/ZgDu97CW8eNhRH2Wsiy8Sq5l5dFnwtIUYjzsXHU77jUy1W5AtGSIw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@8.0.1': - resolution: {integrity: sha512-Tv43P47o6fuHgBL7HLHQg3WKXohW9CEUGjLtnCDW27yJLK0zKUdTTqREbZbycNHA83hewMjde5tF6ekrHu9bAA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dynamic-import@8.0.1': - resolution: {integrity: sha512-AS9GlgKc43tJNRu7yOvLaTko4qmdOb+8M69uNS8i421WLO20eVez7LdG5khKdi8E0LIQpYzzzdGIrdXWnO753g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@8.0.1': - resolution: {integrity: sha512-VzDIYwBlLCpV6mJfloRdJm8HmYnMqs7O+bGha8yfg2kP7jAdxeCw6yZBVBeaKKQUThtSU52iy+3lB7DhYsbOBA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@8.0.1': - resolution: {integrity: sha512-DsZvUUklUmDQ7d2vp+VjqgUWD51mGxhZZ1FPdPP9Hcj0vsgGUKX+zEBGp/vzB1O5PZUxWT/Euq5fu39M9dm9wg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-transform-export-namespace-from@8.0.1': - resolution: {integrity: sha512-bFzznm46bvWGaTYKle3iolbBJ+oPBfUjwCPesxlFE3SQ7DaY9EHf/8Y5ZzrodKJi8JDdcAyaVWaDUSVyhULh0g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@8.0.1': - resolution: {integrity: sha512-rpeXtgELjpIBQH/+YmyFlD9timPEVCyqY+TNednzoeoTYvXSBEeUvYnYE+BK8rB8m6hHiNK7aL9QWKhGifEJCw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-function-name@8.0.1': - resolution: {integrity: sha512-H1L/JfPf3CqmubuaiZaquXKQ8MRs4YWSsgRllkTviM8TafcCNnlvc4/fJZ3rXP8HmFM+/Bg+TlsPehUI9BtDFA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@8.0.1': - resolution: {integrity: sha512-Mowp8X0J6p7ZehLU82B5e65te2uuSeDHyxrEROwEAS2VKXNXssfw5ZMqhY7k9iXTsOv1Xs/49G3lDCj9Vvw8qQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-literals@8.0.1': - resolution: {integrity: sha512-ai7kfPRcfyUV1EszXoF1PvL3IuJoCuH08WSEPoRcJTWfZZ55VL/rcfvbVY16QLA3jjbzzSneQSoCtD3L6OyUjw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@8.0.1': - resolution: {integrity: sha512-Emvtr5zkEGyCNAmt+qKD5EUh8G0RbxV9EZWrDdX0LuVy5tBq1B3fOIslvVF9aCJmpnwS/AvAT53b9LxAZyXlng==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-member-expression-literals@8.0.1': - resolution: {integrity: sha512-3Axi9abnyGsm/hh6DsKPZ1Cr9fTtKqS7w0Ig5g12mU269YclpH8pV3xMln2vPLexXgUp6S6L+I06d9/YOLfRKA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@8.0.1': - resolution: {integrity: sha512-FDdhET8y1YFDNRuoynqSf23WTzbBBpbIB2oRrlFX7YYm9uWtFvJDSD1r/epBSjfPkOjeaaLgRW9xNnt3JGx46A==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@8.0.1': - resolution: {integrity: sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@8.0.1': - resolution: {integrity: sha512-0NEHanXmnFEnfT2dLKTXnu7m8GXFsnxRgteBC2aH21hYMBwAgxu5dcTdi/Eg+ToI1HbZe0CHwz4XRLgRNQhYoQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@8.0.1': - resolution: {integrity: sha512-XKTa2J2MdkmbVEeChq9f7Or0VYcsF0NyVBgytRyeN9F+J+ETAB2SHhfkG4toz/ssuU0i+h/QgJ6ddo5YakSQcA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@8.0.1': - resolution: {integrity: sha512-zCHu+Jr2gTdJE48lN9SV/kXueCW2M79mKtKJc/ttfzzr/jvgdQdCd17RADMqFRQc/25MLxdtjTmlD0HSAMOlIQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-new-target@8.0.1': - resolution: {integrity: sha512-QSQxVg1x4PuOuhWUs4Y9u+x9Y+ER8z6G3tC+bDLBzvoOrNLJrEBQLRnwrTP8e5klihAw6Z+e9X5RjdAKcAGapA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@8.0.1': - resolution: {integrity: sha512-AgCJAmQLF7+PtsK79wJqr4xJ2StHCXlz7JL5CVFP4HejJx25Tk6yl1ZrXvi0cKh3VGDVnfVxefxnrpsBirgpyQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@8.0.1': - resolution: {integrity: sha512-it2DmUyLIA1GQUXlFDEnI+/G89mTgxndnAiZYpW8xYR6LboblfirMqiWJeTna5uypQJg7viTT4D1iEURRtFcfw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-transform-object-rest-spread@8.0.1': - resolution: {integrity: sha512-VmxkDu6bBdbxRzqn6E93hYucug4OVa6svSO19W//vVzNUGAmQzk3QRyHyyEtfcjSLR3NWfRsWwVM9zExLmd+2w==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@8.0.1': - resolution: {integrity: sha512-fDkPXRTRKGm25bAq01q82UM4ypPqdVXCwphUUm4t1dL01fGIG0v8KRvT+4BjhMAtRxtPuI34t5Vs7yjRgs3ZgQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@8.0.1': - resolution: {integrity: sha512-b2OQ74uGliyATcasTjxGy2O/86UI/n+EN4juB4EMfEwTi9j9uq70PuP0L8fW77vfRY66gO/YoTo/WbIdQ/Si1g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@8.0.1': - resolution: {integrity: sha512-WtRS1c94lZGpGHxYLXMEWeoMVcuv8nkiyr8BTs6OYZv7N3Y9xVE8nbdFIl4lDJH6aH8/pLhqAQOL69d/WI9WdA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@8.0.1': - resolution: {integrity: sha512-IIwRqroW0CYQwR6+3pnmu27z+H98poScWdnov8z6osumMeEsFxAFBBsDS2CFk2jFpPlGqVr89jK/HXO6i5DzxQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@8.0.1': - resolution: {integrity: sha512-TrFCGcXaVDh6S5IRhmLSRTY9H80VTCMQWnZtzBRg4RWg3KCLmdmsmj4M15kZAPZfoPkWL/SJb4em3Py/vOiX8g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@8.0.1': - resolution: {integrity: sha512-e+yfOqSYBZaf3PARpiQkjZrpWYgmcFLhK+1tevh2CpHR1O9/36IdyPnAZusESX5nzVV/XZTDAtQBRLa8HPT5Dw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@8.0.1': - resolution: {integrity: sha512-Z/qx4cxUtYR1nt7XWRutObPxDks98fEYsjWbVeKEqZH6y3AGknmgzCqmHf2FHWZCl1DfoPeuJY+3hZ+35D+2tg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@8.0.2': - resolution: {integrity: sha512-aFfsjCRYducRV4dPnpsBbdRkLjboca9FVDg6HZCgy0Ahvk2ZQ/2exmCRC5qS9P6rsWwrmIheNaIM6A1j2F8KMA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@8.0.1': - resolution: {integrity: sha512-02ITRDBesPdTYU0oShAzERwEPzozOUQSXlz3qrt8JGuhalBJQv9z5NjgHJPC9sS3Fsam8gDtfAEpBnqZwUIdjQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-reserved-words@8.0.1': - resolution: {integrity: sha512-+aykZi7ZP3U84veqfJXm3HhPZGddWFi64g7jr0ni6tb1zel+1ey+SL+IRKPoZXFyFqvYEsoqrmx4PyEJRlHl/Q==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@8.0.1': - resolution: {integrity: sha512-MPDpKBrxn+thQay3eJmUiSeHswiT7MkINb48hHkX6OzodB149PKq1kred+lpMebrDzHA+G1ekCQnlYSkyEqAOw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-shorthand-properties@8.0.1': - resolution: {integrity: sha512-JddANd9yPVH8dYgVoNkqAH5BftnsDxFpG51Zas7sc6F3poz5QWcejHNGO8a/57IX5ByjGSzEmYk9Z7ZMa5MWaw==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0 - '@babel/plugin-transform-spread@8.0.1': - resolution: {integrity: sha512-O9Bw9FyxlSw1SlMg3S82/GKNZ0x77RPbHezotEy1JTlIM/vk6WO8jW1iF+iTiKLOXNvi+b+LZ9t77Gi+Q0FhGg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-sticky-regex@8.0.1': - resolution: {integrity: sha512-IsVP6WrZZQdaG2zLmeKwWiI+ua2NB5L1+f77C2/8z2NCDz7uxlIA/lnwocYOJk9PXcOC2sZgRls3LN4XpNduzQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-runtime@7.29.0': + resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-template-literals@8.0.1': - resolution: {integrity: sha512-JXvtj5+BJA9Qv3prDzW2z2DkGTJNmG0BObTdUD03STiu1Jr4fNQkQy3hYZgPL46a2RjcuhwBMYf49BOuJ98gnA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@8.0.1': - resolution: {integrity: sha512-+wJoxgxP2gtey0UMUOMhzMMji2XHO/Uu6MXUh/r5Yhc2jngKzK/wFxY2WNe4UCaRcMvCb4gcnB8wIgFXJsocXg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@8.0.1': - resolution: {integrity: sha512-TAXJepIJ6vZphytTwcf+LuXi2M2ZWI43VCqNw+1ZZLPP/38Z1A8j4Mahvg8kqDgMOSM/cakk+hedTJCiw3jQuQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@8.0.1': - resolution: {integrity: sha512-zjBN9tSMSuomNDfurL69Gf7+v4D2t5uI1mSZaYJDo88SKpbduhCXqtxH7Tx66iCF6caWYwnBzSM0tnCozmQq5Q==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-regex@8.0.1': - resolution: {integrity: sha512-v0oO83cvT5lwbcIVRShpx4vaHD8AvM9IBowsQuTeP+kGmhh3recJQs33Bl6dlo3/2g9amlznLbFGn4VJbPCJqA==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@8.0.1': - resolution: {integrity: sha512-MlQeyS0K7gh0XNeLBMS/3Z07HjDOKhA7xm2L18GyxOXyiFHI9E+ZuQ4mFYmcLjluXsE/Wf6dABIqZvKpKw0Z3w==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/preset-env@8.0.2': - resolution: {integrity: sha512-CUGLn9hNBCF/eXnwdFAWERbniCcXCRvnKwLV9fegeUEIqv7YlU2MepsWMMM54GcILx5XYMnRh+JAL+K5G+mK6g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/preset-modules@0.2.0': - resolution: {integrity: sha512-yz0RBN2fx4fjCeFcTWsWgL7PxSRltvTa0Qg14HkWCU3qS8MO7ZSJlBVbGceynd5C9NsJwwUHNQD3dc6tYO+jqQ==} + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^8.0.0 + '@babel/core': ^7.0.0-0 - '@babel/runtime@8.0.0': - resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 - '@babel/template@8.0.0': - resolution: {integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/traverse@8.0.4': - resolution: {integrity: sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.4': - resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} - engines: {node: ^22.18.0 || >=24.11.0} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} - '@bazel/bazelisk@1.28.1': - resolution: {integrity: sha512-K21x83NXOtd0yb2qzjMES3UV4xEWZ1q1vnXFhADA1u7IoiMVQkJAVQRK3oZ5txpnrGafY15HS+YYr2nmsEP4Tg==} + '@bazel/bazelisk@1.26.0': + resolution: {integrity: sha512-bTNcHdGyEQ9r7SczEYUa0gkEQhJo1ld2BjXI8fWBvsUeoHi03QpUs2HZgDbjjrpQFQqG2ZbO7ihZvH8MjhUTHw==} hasBin: true '@bazel/buildifier@8.2.1': @@ -1601,12 +1576,12 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - '@conventional-changelog/git-client@3.1.0': - resolution: {integrity: sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==} - engines: {node: '>=22'} + '@conventional-changelog/git-client@1.0.1': + resolution: {integrity: sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==} + engines: {node: '>=18'} peerDependencies: - conventional-commits-filter: ^6.0.1 - conventional-commits-parser: ^7.0.1 + conventional-commits-filter: ^5.0.0 + conventional-commits-parser: ^6.0.0 peerDependenciesMeta: conventional-commits-filter: optional: true @@ -1626,395 +1601,551 @@ packages: '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} - '@discoveryjs/json-ext@1.1.0': - resolution: {integrity: sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==} + '@discoveryjs/json-ext@0.6.3': + resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} - '@electric-sql/pglite-tools@0.2.21': - resolution: {integrity: sha512-kv8Z7UmmBVECHud63VblgQLp4A+qSklNP7H22VQqFQGrWFTodc73bubcjgmBqThFsIIiEeAEQQYwWGaK2lSDtA==} + '@electric-sql/pglite-tools@0.2.20': + resolution: {integrity: sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==} peerDependencies: - '@electric-sql/pglite': 0.3.16 + '@electric-sql/pglite': 0.3.15 '@electric-sql/pglite@0.2.17': resolution: {integrity: sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw==} - '@electric-sql/pglite@0.3.16': - resolution: {integrity: sha512-mZkZfOd9OqTMHsK+1cje8OSzfAQcpD7JmILXTl5ahdempjUDdmg4euf1biDex5/LfQIDJ3gvCu6qDgdnDxfJmA==} + '@electric-sql/pglite@0.3.15': + resolution: {integrity: sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==} - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@firebase/ai@2.13.1': - resolution: {integrity: sha512-RhT/VViTPBSplhQSuEp62HhLvfsV+LowMh8ZUo5MMRDzG7oFtSget4Kmg5oHP50hDVyWQuQj6to9iPFEZk08Tw==} + '@firebase/ai@2.7.0': + resolution: {integrity: sha512-PwpCz+TtAMWICM7uQNO0mkSPpUKwrMV4NSwHkbVKDvPKoaQmSlO96vIz+Suw2Ao1EaUUsxYb5LGImHWt/fSnRQ==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x '@firebase/app-types': 0.x - '@firebase/analytics-compat@0.2.28': - resolution: {integrity: sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==} + '@firebase/analytics-compat@0.2.25': + resolution: {integrity: sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/analytics-types@0.8.4': - resolution: {integrity: sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==} + '@firebase/analytics-types@0.8.3': + resolution: {integrity: sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==} - '@firebase/analytics@0.10.22': - resolution: {integrity: sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==} + '@firebase/analytics@0.10.19': + resolution: {integrity: sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==} peerDependencies: '@firebase/app': 0.x - '@firebase/app-check-compat@0.4.5': - resolution: {integrity: sha512-JI17mVcZs34zO6ZeSCrw4U2iohqy+n6GIzkbmsA+TbVjmvFLkUKt3bs5M+qRBteQm/0IWzqSHYFzEQLzDTQebg==} + '@firebase/app-check-compat@0.4.0': + resolution: {integrity: sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/app-check-interop-types@0.3.4': - resolution: {integrity: sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==} + '@firebase/app-check-interop-types@0.3.3': + resolution: {integrity: sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==} - '@firebase/app-check-types@0.5.4': - resolution: {integrity: sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==} + '@firebase/app-check-types@0.5.3': + resolution: {integrity: sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==} - '@firebase/app-check@0.12.0': - resolution: {integrity: sha512-wMeT6HLWRAuW7Cp/5UjWBGKgjPNxWNOoNf4PRIv0weljoGMZVeqbUY7wNBWTI2/31cX1NlXx8gQruDLsUShB3Q==} + '@firebase/app-check@0.11.0': + resolution: {integrity: sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/app-compat@0.5.15': - resolution: {integrity: sha512-HaiSM9TwbGIR4b7F6+UncHWlqdH89eeY7VUskaOGOlI2PxHS5Z+6hHsYGvNLy0SHDE6zyXO+3QSA6a4aqQxsqA==} + '@firebase/app-compat@0.5.7': + resolution: {integrity: sha512-MO+jfap8IBZQ+K8L2QCiHObyMgpYHrxo4Hc7iJgfb9hjGRW/z1y6LWVdT9wBBK+VJ7cRP2DjAiWQP+thu53hHA==} engines: {node: '>=20.0.0'} - '@firebase/app-types@0.9.5': - resolution: {integrity: sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==} + '@firebase/app-types@0.9.3': + resolution: {integrity: sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==} - '@firebase/app@0.15.1': - resolution: {integrity: sha512-iD9+Z5HcPo0Uop5f72/VYMeXwKucBhW7iFrISkJFvQ+lSZikTNgTz0FgAtaaTkAG0pEZSnCymA2Fu49n0rcufQ==} + '@firebase/app@0.14.7': + resolution: {integrity: sha512-o3ZfnOx0AWBD5n/36p2zPoB0rDDxQP8H/A60zDLvvfRLtW8b3LfCyV97GKpJaAVV1JMMl/BC89EDzMyzxFZxTw==} engines: {node: '>=20.0.0'} - '@firebase/auth-compat@0.6.8': - resolution: {integrity: sha512-llcBREUC4iSNKZ6rvwud7Oz9Q7aAWU6KuQLa6pdu7Q+QAQsy4JLw6yFgxwtmzabsgznHmmcsX2UjHLLzqUxi3Q==} + '@firebase/auth-compat@0.6.2': + resolution: {integrity: sha512-8UhCzF6pav9bw/eXA8Zy1QAKssPRYEYXaWagie1ewLTwHkXv6bKp/j6/IwzSYQP67sy/BMFXIFaCCsoXzFLr7A==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/auth-interop-types@0.2.5': - resolution: {integrity: sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==} + '@firebase/auth-interop-types@0.2.4': + resolution: {integrity: sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==} - '@firebase/auth-types@0.13.1': - resolution: {integrity: sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==} + '@firebase/auth-types@0.13.0': + resolution: {integrity: sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==} peerDependencies: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/auth@1.13.3': - resolution: {integrity: sha512-bqiq4uubDN2YyQkdvSWPQeJyXAv2O76ImF41En9b6UhV5JuBVYDoHYrrrE3NzIuGkpFMKagfhMRP4Vz6t+yQSQ==} + '@firebase/auth@1.12.0': + resolution: {integrity: sha512-zkvLpsrxynWHk07qGrUDfCSqKf4AvfZGEqJ7mVCtYGjNNDbGE71k0Yn84rg8QEZu4hQw1BC0qDEHzpNVBcSVmA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@react-native-async-storage/async-storage': ^2.2.0 || ^3.0.0 + '@react-native-async-storage/async-storage': ^2.2.0 peerDependenciesMeta: '@react-native-async-storage/async-storage': optional: true - '@firebase/component@0.7.3': - resolution: {integrity: sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==} + '@firebase/component@0.7.0': + resolution: {integrity: sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==} engines: {node: '>=20.0.0'} - '@firebase/data-connect@0.7.1': - resolution: {integrity: sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg==} + '@firebase/data-connect@0.3.12': + resolution: {integrity: sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==} peerDependencies: '@firebase/app': 0.x - '@firebase/database-compat@2.1.4': - resolution: {integrity: sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==} + '@firebase/database-compat@2.1.0': + resolution: {integrity: sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==} engines: {node: '>=20.0.0'} - '@firebase/database-types@1.0.20': - resolution: {integrity: sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==} + '@firebase/database-types@1.0.16': + resolution: {integrity: sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==} - '@firebase/database@1.1.3': - resolution: {integrity: sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==} + '@firebase/database@1.1.0': + resolution: {integrity: sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==} engines: {node: '>=20.0.0'} - '@firebase/firestore-compat@0.4.11': - resolution: {integrity: sha512-W7o1WdwWq5aABK5Up2ncSvTQs/QGLR/fy7cVpFBNqhsXtxoMtflHf2xBIG6+aoptcuGAobddq4g2Sq27wqHaYw==} + '@firebase/firestore-compat@0.4.4': + resolution: {integrity: sha512-JvxxIgi+D5v9BecjLA1YomdyF7LA6CXhJuVK10b4GtRrB3m2O2hT1jJWbKYZYHUAjTaajkvnos+4U5VNxqkI2w==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/firestore-types@3.0.4': - resolution: {integrity: sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==} + '@firebase/firestore-types@3.0.3': + resolution: {integrity: sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==} peerDependencies: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/firestore@4.16.0': - resolution: {integrity: sha512-qdHMHMvMr0nRMuZyWNR/ArWa0YlPE3C4eAbmxTASJMYXAesKPL0Y54p70moggrNPzaK7MSIIq5RDJJyntQyIYA==} + '@firebase/firestore@4.10.0': + resolution: {integrity: sha512-fgF6EbpoagGWh5Vwfu/7/jYgBFwUCwTlPNVF/aSjHcoEDRXpRsIqVfAFTp1LD+dWAUcAKEK3h+osk8spMJXtxA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/functions-compat@0.4.5': - resolution: {integrity: sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ==} + '@firebase/functions-compat@0.4.1': + resolution: {integrity: sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/functions-types@0.6.4': - resolution: {integrity: sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==} + '@firebase/functions-types@0.6.3': + resolution: {integrity: sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==} - '@firebase/functions@0.13.5': - resolution: {integrity: sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw==} + '@firebase/functions@0.13.1': + resolution: {integrity: sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/installations-compat@0.2.22': - resolution: {integrity: sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==} + '@firebase/installations-compat@0.2.19': + resolution: {integrity: sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/installations-types@0.5.4': - resolution: {integrity: sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==} + '@firebase/installations-types@0.5.3': + resolution: {integrity: sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==} peerDependencies: '@firebase/app-types': 0.x - '@firebase/installations@0.6.22': - resolution: {integrity: sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==} + '@firebase/installations@0.6.19': + resolution: {integrity: sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==} peerDependencies: '@firebase/app': 0.x - '@firebase/logger@0.5.1': - resolution: {integrity: sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==} + '@firebase/logger@0.5.0': + resolution: {integrity: sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==} engines: {node: '>=20.0.0'} - '@firebase/messaging-compat@0.2.27': - resolution: {integrity: sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA==} + '@firebase/messaging-compat@0.2.23': + resolution: {integrity: sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/messaging-interop-types@0.2.5': - resolution: {integrity: sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw==} + '@firebase/messaging-interop-types@0.2.3': + resolution: {integrity: sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==} - '@firebase/messaging@0.13.0': - resolution: {integrity: sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ==} + '@firebase/messaging@0.12.23': + resolution: {integrity: sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==} peerDependencies: '@firebase/app': 0.x - '@firebase/performance-compat@0.2.25': - resolution: {integrity: sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==} + '@firebase/performance-compat@0.2.22': + resolution: {integrity: sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/performance-types@0.2.4': - resolution: {integrity: sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==} + '@firebase/performance-types@0.2.3': + resolution: {integrity: sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==} - '@firebase/performance@0.7.12': - resolution: {integrity: sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==} + '@firebase/performance@0.7.9': + resolution: {integrity: sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==} peerDependencies: '@firebase/app': 0.x - '@firebase/remote-config-compat@0.2.27': - resolution: {integrity: sha512-FYwYWwSbUdza/pRX4NpSBm/Pimntum3jEIBpnDn5Ey1jHNWgjxrE8Z5SB4mCHd5wGCoYd3koJzxARl/VWIEx0Q==} + '@firebase/remote-config-compat@0.2.21': + resolution: {integrity: sha512-9+lm0eUycxbu8GO25JfJe4s6R2xlDqlVt0CR6CvN9E6B4AFArEV4qfLoDVRgIEB7nHKwvH2nYRocPWfmjRQTnw==} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/remote-config-types@0.5.1': - resolution: {integrity: sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==} + '@firebase/remote-config-types@0.5.0': + resolution: {integrity: sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==} - '@firebase/remote-config@0.9.0': - resolution: {integrity: sha512-aNn6/eJhsSC+gXSToiXiYPv3ypLP9lFtzl+/q9kSOBPB7D6rae0Rt2uENZZLXGYbEgHYKQblOhijJAXGbbJjtQ==} + '@firebase/remote-config@0.8.0': + resolution: {integrity: sha512-sJz7C2VACeE257Z/3kY9Ap2WXbFsgsDLfaGfZmmToKAK39ipXxFan+vzB9CSbF6mP7bzjyzEnqPcMXhAnYE6fQ==} peerDependencies: '@firebase/app': 0.x - '@firebase/storage-compat@0.4.3': - resolution: {integrity: sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==} + '@firebase/storage-compat@0.4.0': + resolution: {integrity: sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x - '@firebase/storage-types@0.8.4': - resolution: {integrity: sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==} + '@firebase/storage-types@0.8.3': + resolution: {integrity: sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==} peerDependencies: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/storage@0.14.3': - resolution: {integrity: sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==} + '@firebase/storage@0.14.0': + resolution: {integrity: sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/util@1.15.1': - resolution: {integrity: sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==} + '@firebase/util@1.13.0': + resolution: {integrity: sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==} engines: {node: '>=20.0.0'} - '@firebase/webchannel-wrapper@1.0.6': - resolution: {integrity: sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==} + '@firebase/webchannel-wrapper@1.0.5': + resolution: {integrity: sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==} '@formatjs/ecma402-abstract@2.3.6': resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} @@ -2031,48 +2162,48 @@ packages: '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@google-cloud/cloud-sql-connector@1.11.2': - resolution: {integrity: sha512-ImHGgHunHoAHnQ9Bbsuq9an0rwmMdaPL8nrdQkVNnuIb0GKE+rTyjKNV+po7DJLuk84JPUUbK0cWW9AjPiAblA==} - engines: {node: '>=22'} + '@google-cloud/cloud-sql-connector@1.9.0': + resolution: {integrity: sha512-kCsWuWBCHBdRSyrNHoJ4lIsd6P3JeXQk3OopsS/TCfJzTs2dfEzQvwl5G7Gn7u/rX60m+X7wv4wHDMP2sG5AFA==} + engines: {node: '>=18'} - '@google-cloud/common@6.0.1': - resolution: {integrity: sha512-1uvKzbmAWUdchIYRsg0f4rUmezOamWuVBSWAPAhnYoUE5OiPEx6v6JOxFIdr3MsrqV+6fyLV3EI1vMPlHoeRvw==} + '@google-cloud/common@6.0.0': + resolution: {integrity: sha512-IXh04DlkLMxWgYLIUYuHHKXKOUwPDzDgke1ykkkJPe48cGIS9kkL2U/o0pm4ankHLlvzLF/ma1eO86n/bkumIA==} engines: {node: '>=18'} - '@google-cloud/paginator@6.0.2': - resolution: {integrity: sha512-S60tHt4oHsVvviXY5+Ti5Reg69y5WjBglwRjJqBiVdRHxniW/L+Mc3mp7yNMcxr2Gi6DkDO4aIognNWcUxf3Vg==} + '@google-cloud/paginator@6.0.0': + resolution: {integrity: sha512-g5nmMnzC+94kBxOKkLGpK1ikvolTFCC3s2qtE4F+1EuArcJ7HHC23RDQVt3Ra3CqpUYZ+oXNKZ8n5Cn5yug8DA==} engines: {node: '>=18'} - '@google-cloud/precise-date@5.1.0': - resolution: {integrity: sha512-Z9RVpJUZR+aMF9gPel07xbLURjdT+G4HV6x7jJhreNarQGcT/ZYn1IIfo55rd+tncGvJ4qZDfZnaF6PzqgsQkw==} + '@google-cloud/precise-date@5.0.0': + resolution: {integrity: sha512-9h0Gvw92EvPdE8AK8AgZPbMnH5ftDyPtKm7/KUfcJVaPEPjwGDsJd1QV0H8esBDV4II41R/2lDWH1epBqIoKUw==} engines: {node: '>=18'} '@google-cloud/projectify@4.0.0': resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} engines: {node: '>=14.0.0'} - '@google-cloud/projectify@5.1.0': - resolution: {integrity: sha512-61RXiUnoPBjeIXTzG+WgGLdJlnE3lS7p63rbePw0CEC0tjMGpODKHEr8vFUmuD+iEl7sUWYKbu/ZTk/N2dXw6w==} + '@google-cloud/projectify@5.0.0': + resolution: {integrity: sha512-XXQLaIcLrOAMWvRrzz+mlUGtN6vlVNja3XQbMqRi/V7XJTAVwib3VcKd7oRwyZPkp7rBVlHGcaqdyGRrcnkhlA==} engines: {node: '>=18'} '@google-cloud/promisify@4.1.0': resolution: {integrity: sha512-G/FQx5cE/+DqBbOpA5jKsegGwdPniU6PuIEMt+qxWgFxvxuFOzVmp6zYchtYuwAWV5/8Dgs0yAmjvNZv3uXLQg==} engines: {node: '>=18'} - '@google-cloud/promisify@5.1.0': - resolution: {integrity: sha512-/j9zzWDxsgKg0hMuFBmLGI8ES9xj4DjXvQnDCKl0w5ed7WE3CGsgHmUoC35rvfBXshSW+5StQGnCUD+RBqITKA==} + '@google-cloud/promisify@5.0.0': + resolution: {integrity: sha512-N8qS6dlORGHwk7WjGXKOSsLjIjNINCPicsOX6gyyLiYk7mq3MtII96NZ9N2ahwA2vnkLmZODOIH9rlNniYWvCQ==} engines: {node: '>=18'} - '@google-cloud/pubsub@5.3.1': - resolution: {integrity: sha512-xXAQBVVrFviWz8L8yDKEqv1ziVw/gMSpYHt3IxSYRSY0fTyjis+xOrKJILj8dd8PAUHGnGiAIYuTprflkE9jsQ==} + '@google-cloud/pubsub@5.2.3': + resolution: {integrity: sha512-YKsFl4Qs+nhy20CPNVeafxAt5erQ8LoJuz/gpPAP0WHGQFXnV3KcSZ5HvzgEiUNYsQs/AjjOUdqwnZ4XKaBY/Q==} engines: {node: '>=18'} '@google-cloud/spanner@8.0.0': resolution: {integrity: sha512-IJn+8A3QZJfe7FUtWqHVNo3xJs7KFpurCWGWCiCz3oEh+BkRymKZ1QxfAbU2yGMDzTytLGQ2IV6T2r3cuo75/w==} engines: {node: '>=18'} - '@google/genai@2.12.0': - resolution: {integrity: sha512-LUr972DZosqPUhf9Mb3CIVu/B99woD3QW6ZJV1T9aNgxaoimAZARmo+IyyDsxIL+zouFiYSdA4hzfEWXc9oNIQ==} + '@google/genai@1.38.0': + resolution: {integrity: sha512-V/4CQVQGovvGHuS73lwJwHKR9x33kCij3zz/ReEQ4A7RJaV0U7m4k1mvYhFk55cGZdF5JLKu2S9BTaFuEs5xTA==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -2080,16 +2211,16 @@ packages: '@modelcontextprotocol/sdk': optional: true - '@googleapis/sqladmin@37.0.0': - resolution: {integrity: sha512-LBxSb3V+avoqsTHY/KsSR4/3FGnRfUw8yhGjx6ihTE5w7Y6l1EZkcx62aCCfCNkaigCQDvgiJG2Pq2jxN1sfag==} + '@googleapis/sqladmin@35.2.0': + resolution: {integrity: sha512-ajR9EGLs1pCkKfsXxfbVRnQ7ZPyktKNAuahHoU06CVKguWwQo3b9aFmq06PYnGk1oXc0+tlW+XEamNa/HF4pbQ==} engines: {node: '>=12.0.0'} - '@grpc/grpc-js@1.14.4': - resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} - '@grpc/grpc-js@1.9.16': - resolution: {integrity: sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==} + '@grpc/grpc-js@1.9.15': + resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==} engines: {node: ^8.13.0 || >=10.10.0} '@grpc/proto-loader@0.7.15': @@ -2097,16 +2228,16 @@ packages: engines: {node: '>=6'} hasBin: true - '@grpc/proto-loader@0.8.1': - resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} engines: {node: '>=6'} hasBin: true '@harperfast/extended-iterable@1.0.3': resolution: {integrity: sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==} - '@hono/node-server@1.19.14': - resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 @@ -2115,9 +2246,9 @@ packages: resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} - '@inquirer/ansi@2.0.7': - resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/ansi@2.0.3': + resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} '@inquirer/checkbox@4.3.2': resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} @@ -2128,9 +2259,9 @@ packages: '@types/node': optional: true - '@inquirer/checkbox@5.2.1': - resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/checkbox@5.0.6': + resolution: {integrity: sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2146,9 +2277,9 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.1.1': - resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/confirm@6.0.6': + resolution: {integrity: sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2164,9 +2295,9 @@ packages: '@types/node': optional: true - '@inquirer/core@11.2.1': - resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/core@11.1.3': + resolution: {integrity: sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2182,9 +2313,9 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.2.2': - resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/editor@5.0.6': + resolution: {integrity: sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2200,9 +2331,9 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.1.1': - resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/expand@5.0.6': + resolution: {integrity: sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2218,9 +2349,9 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@3.0.3': - resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/external-editor@2.0.3': + resolution: {integrity: sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2231,9 +2362,9 @@ packages: resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} - '@inquirer/figures@2.0.7': - resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/figures@2.0.3': + resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} '@inquirer/input@4.3.1': resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} @@ -2244,9 +2375,9 @@ packages: '@types/node': optional: true - '@inquirer/input@5.1.2': - resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/input@5.0.6': + resolution: {integrity: sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2262,9 +2393,9 @@ packages: '@types/node': optional: true - '@inquirer/number@4.1.1': - resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/number@4.0.6': + resolution: {integrity: sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2280,9 +2411,9 @@ packages: '@types/node': optional: true - '@inquirer/password@5.1.1': - resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/password@5.0.6': + resolution: {integrity: sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2298,9 +2429,9 @@ packages: '@types/node': optional: true - '@inquirer/prompts@8.5.2': - resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/prompts@8.2.0': + resolution: {integrity: sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2316,9 +2447,9 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.3.1': - resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/rawlist@5.2.2': + resolution: {integrity: sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2334,9 +2465,9 @@ packages: '@types/node': optional: true - '@inquirer/search@4.2.1': - resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/search@4.1.2': + resolution: {integrity: sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2352,9 +2483,9 @@ packages: '@types/node': optional: true - '@inquirer/select@5.2.1': - resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/select@5.0.6': + resolution: {integrity: sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2370,15 +2501,23 @@ packages: '@types/node': optional: true - '@inquirer/type@4.0.7': - resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/type@4.0.3': + resolution: {integrity: sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2391,8 +2530,8 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} '@jasminejs/reporters@1.0.0': @@ -2462,50 +2601,50 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/fs-core@4.64.0': - resolution: {integrity: sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==} + '@jsonjoy.com/fs-core@4.56.10': + resolution: {integrity: sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-fsa@4.64.0': - resolution: {integrity: sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==} + '@jsonjoy.com/fs-fsa@4.56.10': + resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.64.0': - resolution: {integrity: sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==} + '@jsonjoy.com/fs-node-builtins@4.56.10': + resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.64.0': - resolution: {integrity: sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==} + '@jsonjoy.com/fs-node-to-fsa@4.56.10': + resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-utils@4.64.0': - resolution: {integrity: sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==} + '@jsonjoy.com/fs-node-utils@4.56.10': + resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node@4.64.0': - resolution: {integrity: sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==} + '@jsonjoy.com/fs-node@4.56.10': + resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-print@4.64.0': - resolution: {integrity: sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==} + '@jsonjoy.com/fs-print@4.56.10': + resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.64.0': - resolution: {integrity: sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==} + '@jsonjoy.com/fs-snapshot@4.56.10': + resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -2549,53 +2688,53 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@listr2/prompt-adapter-inquirer@4.2.4': - resolution: {integrity: sha512-/KRI2DMD7JGSYaREF0Ygl7AefJ/2ase4Gc5cBiKqT5l4tFjsSJfhFGcc5nSkgl0Sp9LkCQNzl/cqbVJYP2L3dw==} - engines: {node: '>=22.13.0'} + '@listr2/prompt-adapter-inquirer@3.0.5': + resolution: {integrity: sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==} + engines: {node: '>=20.0.0'} peerDependencies: - '@inquirer/prompts': '>= 3 < 9' - listr2: 10.2.1 + '@inquirer/prompts': '>= 3 < 8' + listr2: 9.0.5 - '@lmdb/lmdb-darwin-arm64@3.5.6': - resolution: {integrity: sha512-mY5FG4TjPAkY4P0w+OhHaUka5mDh2TX2WKYIwuKzJ1zeW3VvRgxdam/lGJTquI+bthTx5CSHDW+BAQCnNAzkEA==} + '@lmdb/lmdb-darwin-arm64@3.5.1': + resolution: {integrity: sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==} cpu: [arm64] os: [darwin] - '@lmdb/lmdb-darwin-x64@3.5.6': - resolution: {integrity: sha512-foa+pwitysO8k+xhs7psBFfTKnVgR69NlZRRTHaFVDqphh7AdGpLeyRzKw/ofatr/sN6TiHRRW6mmop0ZrrppQ==} + '@lmdb/lmdb-darwin-x64@3.5.1': + resolution: {integrity: sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==} cpu: [x64] os: [darwin] - '@lmdb/lmdb-linux-arm64@3.5.6': - resolution: {integrity: sha512-HmiyFFdJa38s1heCMSooSPaBSFTHJ3C+ERPp28xAPlDX1YiALJVOgbry065nXd8Y7KISWjnw05zpG1RX8IfftA==} + '@lmdb/lmdb-linux-arm64@3.5.1': + resolution: {integrity: sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==} cpu: [arm64] os: [linux] - '@lmdb/lmdb-linux-arm@3.5.6': - resolution: {integrity: sha512-QR4YRyR5h5Z8eGXrNQjiyo2NNDfqi3tCc9dQG5Is1blCt+qWw1ZoBWhlWAr5d+jshkifMIJjVHzHGKbkKzF8Tw==} + '@lmdb/lmdb-linux-arm@3.5.1': + resolution: {integrity: sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==} cpu: [arm] os: [linux] - '@lmdb/lmdb-linux-x64@3.5.6': - resolution: {integrity: sha512-ADzCuCF2cTNiX9kDScqcz1fjnAkxPpQNneV3KFTdV3wWtVlI2sTGzySoMTgDpinkMMFj1NTJlxA6XR8fwc4hlA==} + '@lmdb/lmdb-linux-x64@3.5.1': + resolution: {integrity: sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==} cpu: [x64] os: [linux] - '@lmdb/lmdb-win32-arm64@3.5.6': - resolution: {integrity: sha512-J7A9aEQsQiv0TYtBGL7NDIPp2lOS8nnl+zm4sWZm1xlsTTaQ4PgD096Adzdrk27rw3UxCkDXdCUa4ax41oztBQ==} + '@lmdb/lmdb-win32-arm64@3.5.1': + resolution: {integrity: sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==} cpu: [arm64] os: [win32] - '@lmdb/lmdb-win32-x64@3.5.6': - resolution: {integrity: sha512-1g7G0knRX2iV/voDu54yxrGqw5Dk0w2oIYb7dgJq8IkOi+m7wbD8Q3QpPFjh0C01G58S88dqGn03len6UPCXsg==} + '@lmdb/lmdb-win32-x64@3.5.1': + resolution: {integrity: sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==} cpu: [x64] os: [win32] - '@material/material-color-utilities@0.4.0': - resolution: {integrity: sha512-dlq6VExJReb8dhjj3a/yTigr3ncNwoFmL5Iy2ENtbDX03EmNeOEdZ+vsaGrj7RTuO+mB7L58II4LCsl4NpM8uw==} + '@material/material-color-utilities@0.3.0': + resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} - '@modelcontextprotocol/sdk@1.29.0': - resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + '@modelcontextprotocol/sdk@1.25.3': + resolution: {integrity: sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -2603,38 +2742,47 @@ packages: '@cfworker/json-schema': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': - resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': - resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} cpu: [x64] os: [darwin] - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': - resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} cpu: [arm64] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': - resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} cpu: [arm] os: [linux] - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': - resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} cpu: [x64] os: [linux] - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': - resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} cpu: [x64] os: [win32] - '@mswjs/interceptors@0.41.9': - resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + '@mswjs/interceptors@0.39.8': + resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==} engines: {node: '>=18'} '@napi-rs/nice-android-arm-eabi@1.1.1': @@ -2750,24 +2898,20 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} - '@napi-rs/wasm-runtime@1.1.6': - resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@nginfra/angular-linking@1.0.11': - resolution: {integrity: sha512-X0fFp9cDIzyS53dstFalPM6nSPtQz/XoBsPFMOmxE66jBIfJGW8W2wQWlk1T+t018eXEC8RgPdKluo6Aw5dm+g==} + '@nginfra/angular-linking@1.0.9': + resolution: {integrity: sha512-25cph8loaVTzSxD6Ll5vjUhm/xxbr1OHLaH/kh464yOMaiK2eKYpNdnIPpfsTFStrwh5/3wKTWTNSCFvjgeTiQ==} peerDependencies: '@angular/compiler-cli': '*' - '@ngtools/webpack@22.1.0-next.4': - resolution: {integrity: sha512-09q8sUc079bfkzcWoVOvBhzR5dapB0KUB/i6jxDRX2an3ph1wy1hEShtEhgfyUTjbxyN2sR8UfkQ1xZ6UHKZQg==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - deprecated: Angular's Webpack support is deprecated. Use the esbuild and Vite-based "@angular/build" package instead. + '@ngtools/webpack@21.2.0-next.2': + resolution: {integrity: sha512-Sv1O31v5JDgYGGQFc1R4+R/McMF84CzZXd/wplGkoYnWjnmYY9/1ZrEGvl/QJ6vLaGZq5WRPuQzuZmH4Suq/2A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler-cli': ^22.0.0 || ^22.1.0-next.0 - typescript: 6.0.2 + '@angular/compiler-cli': ^21.0.0 || ^21.2.0-next.0 + typescript: 5.9.2 webpack: ^5.54.0 '@noble/hashes@1.4.0': @@ -2786,8 +2930,49 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-app@8.2.0': - resolution: {integrity: sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==} + '@npmcli/agent@4.0.0': + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/git@7.0.1': + resolution: {integrity: sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/installed-package-contents@4.0.0': + resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + '@npmcli/node-gyp@5.0.0': + resolution: {integrity: sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/package-json@7.0.4': + resolution: {integrity: sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/promise-spawn@3.0.0': + resolution: {integrity: sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/promise-spawn@9.0.1': + resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/redact@4.0.0': + resolution: {integrity: sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/run-script@10.0.3': + resolution: {integrity: sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@octokit/auth-app@8.1.2': + resolution: {integrity: sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==} engines: {node: '>= 20'} '@octokit/auth-oauth-app@9.0.3': @@ -2810,8 +2995,8 @@ packages: resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} engines: {node: '>= 20'} '@octokit/graphql-schema@15.26.1': @@ -2854,8 +3039,8 @@ packages: resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} - '@octokit/request@10.0.11': - resolution: {integrity: sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==} + '@octokit/request@10.0.7': + resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} engines: {node: '>= 20'} '@octokit/rest@22.0.1': @@ -2878,8 +3063,8 @@ packages: resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==} engines: {node: '>=14'} - '@opentelemetry/api@1.9.1': - resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} '@opentelemetry/context-async-hooks@1.30.1': @@ -2888,8 +3073,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/context-async-hooks@2.9.0': - resolution: {integrity: sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==} + '@opentelemetry/context-async-hooks@2.5.1': + resolution: {integrity: sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -2900,8 +3085,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.9.0': - resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} + '@opentelemetry/core@2.5.1': + resolution: {integrity: sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3064,12 +3249,12 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.39.0': - resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} + '@opentelemetry/semantic-conventions@1.34.0': + resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.43.0': - resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + '@opentelemetry/semantic-conventions@1.39.0': + resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} engines: {node: '>=14'} '@opentelemetry/sql-common@0.40.1': @@ -3078,8 +3263,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/types@0.112.0': + resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} @@ -3169,41 +3354,38 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} - '@paulirish/trace_engine@0.0.65': - resolution: {integrity: sha512-Qsm6F5C8xf6ZzQXbQc2+wcpe6sggfs/gvc/ytqSurdvYg3kyW0ECHCqE0CWBKZpqgjVfPNX9c7SCS3r2nEIRGg==} - - '@peculiar/asn1-cms@2.8.0': - resolution: {integrity: sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==} + '@paulirish/trace_engine@0.0.61': + resolution: {integrity: sha512-/O08DwmUqIlJjUSPSZbNF8lWnlxaMsIOV6sS+uDKCxBd5i1psAmjEoG3JAqR6+nHD8X+YY474NW7SxUH/K+/kQ==} - '@peculiar/asn1-csr@2.8.0': - resolution: {integrity: sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==} + '@peculiar/asn1-cms@2.6.1': + resolution: {integrity: sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==} - '@peculiar/asn1-ecc@2.8.0': - resolution: {integrity: sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==} + '@peculiar/asn1-csr@2.6.1': + resolution: {integrity: sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==} - '@peculiar/asn1-pfx@2.8.0': - resolution: {integrity: sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==} + '@peculiar/asn1-ecc@2.6.1': + resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==} - '@peculiar/asn1-pkcs8@2.8.0': - resolution: {integrity: sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==} + '@peculiar/asn1-pfx@2.6.1': + resolution: {integrity: sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==} - '@peculiar/asn1-pkcs9@2.8.0': - resolution: {integrity: sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==} + '@peculiar/asn1-pkcs8@2.6.1': + resolution: {integrity: sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==} - '@peculiar/asn1-rsa@2.8.0': - resolution: {integrity: sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==} + '@peculiar/asn1-pkcs9@2.6.1': + resolution: {integrity: sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==} - '@peculiar/asn1-schema@2.8.0': - resolution: {integrity: sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==} + '@peculiar/asn1-rsa@2.6.1': + resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==} - '@peculiar/asn1-x509-attr@2.8.0': - resolution: {integrity: sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==} + '@peculiar/asn1-schema@2.6.0': + resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==} - '@peculiar/asn1-x509@2.8.0': - resolution: {integrity: sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==} + '@peculiar/asn1-x509-attr@2.6.1': + resolution: {integrity: sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==} - '@peculiar/utils@2.0.3': - resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} + '@peculiar/asn1-x509@2.6.1': + resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==} '@peculiar/x509@1.14.3': resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} @@ -3212,7 +3394,7 @@ packages: '@phenomnomnominal/tsquery@4.2.0': resolution: {integrity: sha512-hR2U3uVcrrdkuG30ItQ+uFDs4ncZAybxWG0OjTE8ptPzVoU7GVeXpy+vMU8zX9EbmjGeITPw/su5HjYQyAH8bA==} peerDependencies: - typescript: 6.0.2 + typescript: 5.9.2 '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -3222,28 +3404,28 @@ packages: resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} - '@pnpm/crypto.hash@1000.2.2': - resolution: {integrity: sha512-W8pLZvXWLlGG5p0Z2nCvtBhlM6uuTcbAbsS15wlGS31jBBJKJW2udLoFeM7qfWPo7E2PqRPGxca7APpVYAjJhw==} + '@pnpm/crypto.hash@1000.2.1': + resolution: {integrity: sha512-Kgo3bgYbdKkC5xFvvQshbHa+Nru7k50D91+yyq7enp4Ur2EMp4wg5oXleaC5xu5hC9A/1eSCRI8npCioplxG4A==} engines: {node: '>=18.12'} '@pnpm/crypto.polyfill@1000.1.0': resolution: {integrity: sha512-tNe7a6U4rCpxLMBaR0SIYTdjxGdL0Vwb3G1zY8++sPtHSvy7qd54u8CIB0Z+Y6t5tc9pNYMYCMwhE/wdSY7ltg==} engines: {node: '>=18.12'} - '@pnpm/dependency-path@1001.1.10': - resolution: {integrity: sha512-PNImtV2SmNTDpLi4HdN86tJPmsOeIxm4VhmxgBVsMrJPEBfkNEWFcflR3wU6XVn/26g9qWdvlNHaawtCjeB93Q==} + '@pnpm/dependency-path@1001.1.9': + resolution: {integrity: sha512-C1V4H54GyMfLL47q93PmdVRJkJyNVEE6Ht6cFrMSsjgsR7fxXWqjlem7OaA9MMjSTBB/d/g9mV4xZnoT/HAkDQ==} engines: {node: '>=18.12'} - '@pnpm/graceful-fs@1000.1.0': - resolution: {integrity: sha512-EsMX4slK0qJN2AR0/AYohY5m0HQNYGMNe+jhN74O994zp22/WbX+PbkIKyw3UQn39yQm2+z6SgwklDxbeapsmQ==} + '@pnpm/graceful-fs@1000.0.1': + resolution: {integrity: sha512-JnzaAVFJIEgwTcB55eww8N3h5B6qJdZqDA2wYkSK+OcTvvMSQb9c2STMhBP6GfkWygG1fs3w8D7JRx9SPZnxJg==} engines: {node: '>=18.12'} '@pnpm/network.ca-file@1.0.2': resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@3.0.3': - resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} + '@pnpm/npm-conf@3.0.2': + resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==} engines: {node: '>=12'} '@pnpm/types@1001.3.0': @@ -3255,119 +3437,127 @@ packages: peerDependencies: '@opentelemetry/api': ^1.8 - '@puppeteer/browsers@3.0.6': - resolution: {integrity: sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==} - engines: {node: '>=22.12.0'} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@puppeteer/browsers@2.12.1': + resolution: {integrity: sha512-fXa6uXLxfslBlus3MEpW8S6S9fe5RwmAE5Gd8u3krqOwnkZJV3/lQJiY3LaFdTctLLqJtyMgEUGkbDnRNf6vbQ==} + engines: {node: '>=18'} hasBin: true - peerDependencies: - proxy-agent: '>=8.0.1' - yauzl: ^2.10.0 || ^3.4.0 - peerDependenciesMeta: - proxy-agent: - optional: true - yauzl: - optional: true - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@rolldown/binding-android-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@rolldown/binding-darwin-x64@1.0.0-rc.3': + resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': + resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': + resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': + resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': + resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': + resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} - engines: {node: ^20.19.0 || >=22.12.0} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': + resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} + engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': + resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - '@rollup/plugin-commonjs@29.0.3': - resolution: {integrity: sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==} + '@rollup/plugin-commonjs@29.0.0': + resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -3384,8 +3574,17 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -3393,147 +3592,147 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.62.2': - resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.2': - resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.2': - resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.2': - resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.2': - resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.2': - resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.62.2': - resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.62.2': - resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.2': - resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.2': - resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.62.2': - resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.62.2': - resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.2': - resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.62.2': - resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.62.2': - resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.62.2': - resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.2': - resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.2': - resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.2': - resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.62.2': - resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.2': - resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.2': - resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.2': - resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.2': - resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} cpu: [x64] os: [win32] - '@schematics/angular@22.1.0-next.4': - resolution: {integrity: sha512-TVXYXc+0avvQzfxfGS1d0OwiS67vCZv6Lf5xte8+j3dGKoeWBaR8MGzajOhisy8XVySqhKcje2a8NOlQqXz+5g==} - engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@schematics/angular@21.2.0-next.2': + resolution: {integrity: sha512-INkmbKiw+IWbg049CYyA4V9FEDH2jRC6FhPDOGIP5bRPssJrffmFSCpm+8p0XuKiIPyDCYeRCsbFA2y0rfnNzw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} '@sentry/core@9.47.1': resolution: {integrity: sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==} @@ -3565,13 +3764,29 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 '@opentelemetry/semantic-conventions': ^1.34.0 - '@simple-libs/child-process-utils@2.0.0': - resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} - engines: {node: '>=22'} + '@sigstore/bundle@4.0.0': + resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/core@3.1.0': + resolution: {integrity: sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/protobuf-specs@0.5.0': + resolution: {integrity: sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@4.1.0': + resolution: {integrity: sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/tuf@4.0.1': + resolution: {integrity: sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==} + engines: {node: ^20.17.0 || >=22.9.0} - '@simple-libs/stream-utils@2.0.0': - resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} - engines: {node: '>=22'} + '@sigstore/verify@3.1.0': + resolution: {integrity: sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==} + engines: {node: ^20.17.0 || >=22.9.0} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -3583,12 +3798,16 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - '@stackblitz/sdk@1.11.1': - resolution: {integrity: sha512-5wl1j3IhtmE7POTl2W9VGQdbQyBQrRsF3jcYBK+0V353AuO8H2GFZkiFNyTRxL+TDY3HIFCHc3G983Osmg76Lg==} + '@stackblitz/sdk@1.11.0': + resolution: {integrity: sha512-DFQGANNkEZRzFk1/rDP6TcFdM82ycHE+zfl9C/M/jXlH68jiqHWHFMQURLELoD8koxvu/eW5uhg94NSAZlYrUQ==} '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -3604,8 +3823,16 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@4.1.0': + resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3646,14 +3873,20 @@ packages: '@types/ejs@3.1.5': resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/events@3.0.3': resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} - '@types/express-serve-static-core@4.19.9': - resolution: {integrity: sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==} + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} @@ -3664,11 +3897,11 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - '@types/gensync@1.0.5': - resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} + '@types/git-raw-commits@5.0.1': + resolution: {integrity: sha512-sd4kgxJbuZF0RDy6cX7KlKSGiwqB1mqn8nriUbxt5e1F+MO/N4hJlhaYn0Omw4g2biClFpT5Mre07x7OkGt8tg==} - '@types/google.maps@3.65.2': - resolution: {integrity: sha512-e52bmOhGCQSNabFpL48iQlwJybq6rfns8NUVJ20MR7CdPlHQ2RmSCnPbJfrUYJfogrE4OiHQTZ4LXpop+eer1w==} + '@types/google.maps@3.58.1': + resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==} '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -3679,20 +3912,14 @@ packages: '@types/jasmine@6.0.0': resolution: {integrity: sha512-18lgGsLmEh3VJk9eZ5wAjTISxdqzl6YOwu8UdMpolajN57QOCNbl+AbHUd+Yu9ItrsFdB+c8LSZSGNg8nHaguw==} - '@types/jsesc@2.5.1': - resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/less@3.0.8': - resolution: {integrity: sha512-Gjm4+H9noDJgu5EdT3rUw5MhPBag46fiOy27BefvWkNL8mlZnKnCaVVVTLKj6RYXed9b62CPKnPav9govyQDzA==} - - '@types/luxon@3.7.2': - resolution: {integrity: sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q==} + '@types/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -3703,11 +3930,11 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.20.1': - resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/node@22.19.11': + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/node@24.10.9': + resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3727,8 +3954,8 @@ packages: '@types/q@0.0.32': resolution: {integrity: sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug==} - '@types/qs@6.15.1': - resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -3736,9 +3963,6 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -3806,14 +4030,17 @@ packages: '@types/yarnpkg__lockfile@1.1.9': resolution: {integrity: sha512-GD4Fk15UoP5NLCNor51YdfL9MSdldKCqOC9EssrRw3HVfar9wUZ5y8Lfnp+qVD6hIinLr8ygklDYnmlnlQo12Q==} - '@types/youtube@0.3.0': - resolution: {integrity: sha512-a+JnwvmYfCShUv2fPs9JL/rUr9JGx5BPjSn7sp3V5UEyyj2AIVkSCRlVoe4CjFBrkBIv9jMpOPakfitMq7tegA==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@vitejs/plugin-basic-ssl@2.3.0': - resolution: {integrity: sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==} + '@types/youtube@0.1.2': + resolution: {integrity: sha512-n1/KqusanheyQRWHamNZv8K3kydlRqyEsZEKxMTeNWXQTC15lZprITCUt+WgL1vAIvKHCjPBIWz/gf/KQLsB3g==} + + '@vitejs/plugin-basic-ssl@2.1.4': + resolution: {integrity: sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} peerDependencies: - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.0.0 || ^7.0.0 '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3860,8 +4087,8 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@xmldom/xmldom@0.8.13': - resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} engines: {node: '>=10.0.0'} '@xtuc/ieee754@1.2.0': @@ -3879,9 +4106,9 @@ packages: abbrev@1.0.9: resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==} - abbrev@5.0.0: - resolution: {integrity: sha512-/XrFJgzQQQHpti1raDJC6m4ws6aNktmjBlhk8Fdlk7LwCEuDoieEJJY9OFHjfiFJFFRM2tK+Ky/IsfbbmlMu1w==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -3911,8 +4138,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} acorn@7.4.1: @@ -3920,8 +4147,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true @@ -3929,8 +4156,8 @@ packages: resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==} engines: {node: '>=8.9'} - adm-zip@0.5.18: - resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} engines: {node: '>=12.0'} agent-base@4.3.0: @@ -3945,10 +4172,6 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - agent-base@9.0.0: - resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} - engines: {node: '>= 20'} - ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3965,14 +4188,17 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.15.0: - resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - algoliasearch@5.55.2: - resolution: {integrity: sha512-OyacJsaeuLUvGWOynNqYc6sx88XvyoG39wMT8SYqL3l9wwaorDW/LPRbUPfhzw0bWsUWzNCZTnFYOrWFBKsUaw==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + algoliasearch@5.47.0: + resolution: {integrity: sha512-AGtz2U7zOV4DlsuYV84tLp2tBbA7RPtLA44jbVH4TTpDcc1dIWmULjHSsunlhscbzDydnjuFlNhflR3nV4VJaQ==} engines: {node: '>= 14.0.0'} amdefine@1.0.1: @@ -4047,10 +4273,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - argue-cli@3.1.0: - resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} - engines: {node: '>=22'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -4099,8 +4321,8 @@ packages: asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - asn1js@3.0.10: - resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} + asn1js@3.0.7: + resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} engines: {node: '>=12.0.0'} assert-plus@1.0.0: @@ -4137,8 +4359,8 @@ packages: atomically@2.1.1: resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} - autoprefixer@10.5.2: - resolution: {integrity: sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==} + autoprefixer@10.4.24: + resolution: {integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -4154,36 +4376,44 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} engines: {node: '>=4'} - b4a@1.8.1: - resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + b4a@1.7.4: + resolution: {integrity: sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==} peerDependencies: react-native-b4a: '*' peerDependenciesMeta: react-native-b4a: optional: true - babel-loader@10.1.1: - resolution: {integrity: sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==} + babel-loader@10.0.0: + resolution: {integrity: sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==} engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0} peerDependencies: - '@babel/core': ^7.12.0 || ^8.0.0-beta.1 - '@rspack/core': ^1.0.0 || ^2.0.0-0 + '@babel/core': ^7.12.0 webpack: '>=5.61.0' - peerDependenciesMeta: - '@rspack/core': - optional: true - webpack: - optional: true - babel-plugin-polyfill-corejs3@1.0.0: - resolution: {integrity: sha512-yIkslVjbmml2Xjb6XhFW7lISXHsqk6cesxTdDsXoMom4Lnb99DbD3OQbSOoM5Z+ASh8YXYaLAsRQrU2Jeh3Qig==} - engines: {node: ^20.19.0 || >=22.12.0} + babel-plugin-polyfill-corejs2@0.4.15: + resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.0: + resolution: {integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==} peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0 + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.6: + resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 bail@1.0.5: resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} @@ -4194,20 +4424,20 @@ packages: balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} + balanced-match@4.0.2: + resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} + engines: {node: 20 || >=22} - bare-events@2.9.1: - resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} peerDependencies: bare-abort-controller: '*' peerDependenciesMeta: bare-abort-controller: optional: true - bare-fs@4.7.4: - resolution: {integrity: sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==} + bare-fs@4.5.4: + resolution: {integrity: sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -4215,25 +4445,26 @@ packages: bare-buffer: optional: true - bare-path@3.1.1: - resolution: {integrity: sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==} + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} - bare-stream@2.13.3: - resolution: {integrity: sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==} + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} peerDependencies: - bare-abort-controller: '*' bare-buffer: '*' bare-events: '*' peerDependenciesMeta: - bare-abort-controller: - optional: true bare-buffer: optional: true bare-events: optional: true - bare-url@2.4.5: - resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} + bare-url@2.3.2: + resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4242,9 +4473,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.43: - resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} - engines: {node: '>=6.0.0'} + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true basic-auth-connect@1.1.0: @@ -4254,8 +4484,8 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} - basic-ftp@5.3.1: - resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + basic-ftp@5.1.0: + resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} batch@0.6.1: @@ -4264,8 +4494,8 @@ packages: bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - beasties@0.4.2: - resolution: {integrity: sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw==} + beasties@0.4.1: + resolution: {integrity: sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==} engines: {node: '>=18.0.0'} before-after-hook@4.0.0: @@ -4295,16 +4525,16 @@ packages: engines: {node: '>=6.9.x'} hasBin: true - body-parser@1.20.6: - resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - bonjour-service@1.4.3: - resolution: {integrity: sha512-2Kd5UYlFUVgAKMTyuBLl6w49wqfOnbxHqmuH0oCl/n7TfAikR0zoowNOP5BU4dfXmm+Vr9JyEN370auSMx+CNg==} + bonjour-service@1.3.0: + resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -4313,27 +4543,27 @@ packages: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.1.2: - resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.6: - resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserstack-local@1.5.13: - resolution: {integrity: sha512-7helY+Ms3ss4BtIQZTIyshdAFZSvS9A7ZpEB9stRaobeZ9BM1BkJFTuMakQNTOj78llv0+/qDI5Ak+bkGWV1xg==} + browserstack-local@1.5.10: + resolution: {integrity: sha512-b9VCPzbcZ9heJIlMsexE/zj3D5Y0l0v2lSalKDmjCvdX//KsUBA+g1evWx6yjkMX1DLdfuEBe8fMn4iK7S1vUA==} browserstack@1.5.3: resolution: {integrity: sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==} @@ -4341,6 +4571,9 @@ packages: browserstack@1.6.1: resolution: {integrity: sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} @@ -4380,12 +4613,16 @@ packages: resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} engines: {node: '>=6.0.0'} + cacache@20.0.3: + resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} + engines: {node: ^20.17.0 || >=22.9.0} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -4414,8 +4651,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001805: - resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} canonical-path@0.0.2: resolution: {integrity: sha512-y8EIEvL+IW81S4hRQWCRFtly+g1cc1G+wxHpjhYR9jI2+JJjWiaKnkH8mmvNHOMOAd9fzgARDO3AEzjuR51qaA==} @@ -4462,8 +4699,8 @@ packages: character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - chardet@2.2.0: - resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} checkpoint-stream@0.1.2: resolution: {integrity: sha512-eYXIcydL3mPjjEVLxHdi1ISgTwmxGJZ8vyJ3lYVvFTDRyTOZMTbKZdRJqiA7Gi1rPcwOyyzcrZmGLL8ff7e69w==} @@ -4472,6 +4709,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -4489,9 +4730,8 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - chromium-bidi@16.0.1: - resolution: {integrity: sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==} - engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0'} + chromium-bidi@14.0.0: + resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==} peerDependencies: devtools-protocol: '*' @@ -4538,8 +4778,8 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-truncate@5.2.0: - resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} cli-width@4.1.0: @@ -4625,6 +4865,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4688,25 +4932,21 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + conventional-commits-filter@5.0.0: + resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} - conventional-commits-filter@6.0.1: - resolution: {integrity: sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==} - engines: {node: '>=22'} - - conventional-commits-parser@7.1.0: - resolution: {integrity: sha512-DPp6hkUjvwIivxbkrTiLXeRswNv1A/4GFA2X6scXma0AMa9632V3TwxmrlkUIEtUktiM3Ln+RrSH2xlP3/jUTw==} - engines: {node: '>=22'} + conventional-commits-parser@6.2.1: + resolution: {integrity: sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==} + engines: {node: '>=18'} hasBin: true convert-source-map@1.9.0: @@ -4726,18 +4966,17 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} - copy-webpack-plugin@14.0.0: - resolution: {integrity: sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==} - engines: {node: '>= 20.9.0'} + copy-webpack-plugin@13.0.1: + resolution: {integrity: sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==} + engines: {node: '>= 18.12.0'} peerDependencies: webpack: ^5.1.0 - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -4753,11 +4992,11 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - cosmiconfig@9.0.2: - resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} peerDependencies: - typescript: 6.0.2 + typescript: 5.9.2 peerDependenciesMeta: typescript: optional: true @@ -4791,18 +5030,18 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - csp_evaluator@1.1.8: - resolution: {integrity: sha512-EwOnfYuNbTytvbMKsLixTrRgnjOa0WZCxGy8A9nnSYAicrdwn+T/epU/yjgymmOxlgKnvH+8wXt+7p/8ak5Feg==} + csp_evaluator@1.1.5: + resolution: {integrity: sha512-EL/iN9etCTzw/fBnp0/uj0f5BOOGvZut2mzsiiBZ/FdT6gFQCKRO/tmcKOxn5drWZ2Ndm/xBb1SI4zwWbGtmIw==} css-functions-list@3.3.3: resolution: {integrity: sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==} engines: {node: '>=12'} - css-loader@7.1.4: - resolution: {integrity: sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==} + css-loader@7.1.3: + resolution: {integrity: sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA==} engines: {node: '>= 18.12.0'} peerDependencies: - '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0 + '@rspack/core': 0.x || 1.x webpack: ^5.27.0 peerDependenciesMeta: '@rspack/core': @@ -4856,8 +5095,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@4.4.0: - resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} date-format@4.0.14: resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} @@ -4921,6 +5160,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-equal-in-any-order@2.1.0: + resolution: {integrity: sha512-9FklcFjcehm1yBWiOYtmazJOiMbT+v81Kq6nThIuXbWLWIZMX3ZI+QoLf7wCi0T8XzTAXf6XqEdEyVrjZkhbGA==} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -5000,11 +5242,11 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - devtools-protocol@0.0.1625959: - resolution: {integrity: sha512-wRBSU330hwOLLcb3N4NIe3eFs6MgT6ku3AiZONjnTSJ7f3dVchJfn6nE0Lfos9jK1na15bgp7xLhaCx40Y47NQ==} + devtools-protocol@0.0.1527314: + resolution: {integrity: sha512-UohCFOlzpPPD/IcsxM0k4lVZp/GfhPVJ6l2No5XX+LknpGisPWJe17oOHQhZTHf6ThUFIMwHO6bSEZUq/6oP7w==} - devtools-protocol@0.0.1638949: - resolution: {integrity: sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==} + devtools-protocol@0.0.1566079: + resolution: {integrity: sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==} dgeni-packages@0.30.0: resolution: {integrity: sha512-k5y9HSwJzWgc8S7bHxejojJfuo2heltY9lTa5UgfqoBv2vubCwqXhVxFBXr3nKfymc9YOALCnk10JYeOdVLLWQ==} @@ -5077,6 +5319,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + duplexify@3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} @@ -5095,13 +5340,13 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@6.0.1: - resolution: {integrity: sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==} + ejs@4.0.1: + resolution: {integrity: sha512-krvQtxc0btwSm/nvnt1UpnaFDFVJpJ0fdckmALpCgShsr/iGYHTnJiUliZTgmzq/UxTX33TtOQVKaNigMQp/6Q==} engines: {node: '>=0.12.18'} hasBin: true - electron-to-chromium@1.5.389: - resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -5119,10 +5364,6 @@ packages: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} - empathic@2.0.1: - resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} - engines: {node: '>=14'} - enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} @@ -5144,12 +5385,12 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.9: - resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} + engine.io@6.6.5: + resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.24.2: - resolution: {integrity: sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} enquirer@2.4.1: @@ -5170,14 +5411,14 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} - entities@8.0.0: - resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} - engines: {node: '>=20.19.0'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -5186,6 +5427,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true @@ -5193,12 +5437,8 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract-get@1.0.0: - resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} - engines: {node: '>= 0.4'} - - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -5209,19 +5449,19 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-to-primitive@1.3.4: - resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} es6-promise@4.2.8: @@ -5230,13 +5470,18 @@ packages: es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - esbuild-wasm@0.28.1: - resolution: {integrity: sha512-p/GD4E8oYRjg3kjdKrnMb0s4PzXgJF42e0MF4H0+ACyK/kIlFRp3e0fzOleIG+wBBm6MM3XQrbpe7soEA+vJIA==} + esbuild-wasm@0.27.2: + resolution: {integrity: sha512-eUTnl8eh+v8UZIZh4MrMOKDAc8Lm7+NqP3pyuTORGFY1s/o9WoiJgKnwXy+te2J3hX7iRbFSHEyig7GsPeeJyw==} engines: {node: '>=18'} hasBin: true - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true @@ -5322,6 +5567,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -5345,8 +5593,8 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.1.0: - resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} eventsource@3.0.7: @@ -5372,14 +5620,20 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@8.5.2: - resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' - express@4.22.2: - resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} + express-rate-limit@8.2.1: + resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} express@5.2.1: @@ -5389,6 +5643,11 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + extsprintf@1.3.0: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} @@ -5397,6 +5656,9 @@ packages: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} engines: {node: '> 0.1.90'} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5419,11 +5681,11 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} @@ -5436,6 +5698,9 @@ packages: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} engines: {node: '>=0.8.0'} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5456,6 +5721,13 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + filesize@6.4.0: + resolution: {integrity: sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==} + engines: {node: '>= 0.4.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5480,13 +5752,13 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firebase-tools@15.23.0: - resolution: {integrity: sha512-YIu1w84GiJAoQfCDBvrK77Qnu19sh00YufGvrWLc9kus9p8j5NkFOGfSpIAiEHu5VliTZUqBhqDwA/i7fuN0GQ==} + firebase-tools@15.2.1: + resolution: {integrity: sha512-S75K4L/CsYSpyoOZAWHeTrx5TrpNCdUeSutr9oto+yBgg5qfbtnrGUxP+iPiBK6g4pdQFzf4p6rN1c7RoGE09A==} engines: {node: '>=20.0.0 || >=22.0.0 || >=24.0.0'} hasBin: true - firebase@12.16.0: - resolution: {integrity: sha512-CNw6hFBdONkzF8UGLDx/RDRY9gVa5VmJNHd7qi4gdmA3ZuLkuOrhmWefB2l+FN+OxFpN77Itq7aO6zlTi780ag==} + firebase@12.8.0: + resolution: {integrity: sha512-S1tCIR3ENecee0tY2cfTHfMkXqkitHfbsvqpCtvsT0Zi9vDB7A4CodAjHfHCjVvu/XtGy1LHLjOasVcF10rCVw==} flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} @@ -5496,19 +5768,19 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - folder-hash@4.1.3: - resolution: {integrity: sha512-94fj+fXj1XHT8zGumUy/VlyFARc/yrslKJ2+vjrP/U6ftTdL7u68+gQhvSBjz9wrwTuty6BpZ7JsbEK5OU9RNw==} + folder-hash@4.1.1: + resolution: {integrity: sha512-1ZSlKJSbET3XpglnEXC9g+QF4QRZhqHIjpFfa4pAMfO4tu/XYPafpeHEX6zOFS2EolOIXr0lPh1eSjmdWItX2w==} engines: {node: '>=10.10.0'} hasBin: true - follow-redirects@1.16.0: - resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -5531,8 +5803,8 @@ packages: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: @@ -5557,18 +5829,25 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5580,8 +5859,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.2.0: - resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -5599,10 +5878,6 @@ packages: resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} - gaxios@7.2.0: - resolution: {integrity: sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==} - engines: {node: '>=18'} - gaze@1.1.3: resolution: {integrity: sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==} engines: {node: '>= 4.0.0'} @@ -5615,10 +5890,6 @@ packages: resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} engines: {node: '>=18'} - gcp-metadata@8.1.3: - resolution: {integrity: sha512-ziTrzUhhpL9Zk5k0HHzgP/KIpWDJT0VMBC/ynt/QIBvTW+UUcSivQRl6VlwTf/EilDxtSWklHoRsKy1c4k+59w==} - engines: {node: '>=18'} - generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} @@ -5631,8 +5902,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -5643,6 +5914,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -5651,6 +5926,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-uri@6.0.5: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} engines: {node: '>= 14'} @@ -5658,6 +5936,11 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + git-raw-commits@5.0.0: + resolution: {integrity: sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==} + engines: {node: '>=18'} + hasBin: true + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5678,6 +5961,9 @@ packages: peerDependencies: tslib: '2' + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -5689,9 +5975,9 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} + glob@13.0.3: + resolution: {integrity: sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==} + engines: {node: 20 || >=22} glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} @@ -5740,16 +6026,12 @@ packages: resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} engines: {node: '>=18'} - google-auth-library@10.9.0: - resolution: {integrity: sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==} - engines: {node: '>=18'} - google-auth-library@9.15.1: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} - google-gax@5.0.7: - resolution: {integrity: sha512-EhiqaWWJ+9h7sCcKJTsoo6tMcjokVHhWsbSuWCnZJT4vIBP3y4mAoFLnt9SzgkVZeq24ZsFaArr06nnYYku2yA==} + google-gax@5.0.6: + resolution: {integrity: sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==} engines: {node: '>=18'} google-logging-utils@0.0.2: @@ -5760,12 +6042,8 @@ packages: resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} - google-logging-utils@1.1.4: - resolution: {integrity: sha512-LXxE6ND+JHhDPYtPFt3oeWrjxFPrnRZCZ4At6lpbi1ZDSAN7bmGET9s53vvARbxT93p+xpeDUbc/nCR4C+7vcw==} - engines: {node: '>=14'} - - googleapis-common@8.0.2: - resolution: {integrity: sha512-5MXeQzIZaqCH7B+HJWqhQm946VARpZep6acbWSr/fcgF2cQANq7allgX+i/G0EqF0WyUxB277gtWMzRYHMl9tg==} + googleapis-common@8.0.1: + resolution: {integrity: sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==} engines: {node: '>=18.0.0'} gopd@1.2.0: @@ -5778,14 +6056,14 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql-tag@2.12.7: - resolution: {integrity: sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==} + graphql-tag@2.12.6: + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} engines: {node: '>=10'} peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - graphql@16.14.2: - resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + graphql@16.12.0: + resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} grpc-gcp@1.0.1: @@ -5803,8 +6081,8 @@ packages: handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} - handlebars@4.7.9: - resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} hasBin: true @@ -5860,8 +6138,8 @@ packages: resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} engines: {node: '>=8'} - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} hast-util-from-parse5@5.0.3: @@ -5896,14 +6174,10 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - hono@4.12.30: - resolution: {integrity: sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==} + hono@4.11.9: + resolution: {integrity: sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==} engines: {node: '>=16.9.0'} - hosted-git-info@10.1.1: - resolution: {integrity: sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -5911,6 +6185,14 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + hosted-git-info@9.0.2: + resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} + engines: {node: ^20.17.0 || >=22.9.0} + hpack.js@2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} @@ -5936,6 +6218,9 @@ packages: htmlparser2@3.10.1: resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} @@ -5947,19 +6232,23 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-link-header@1.1.4: - resolution: {integrity: sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw==} + http-link-header@1.1.3: + resolution: {integrity: sha512-3cZ0SRL8fb9MUlU3mKM61FcQvPfXx2dBrZW3Vbg5CXa8jFlK8OaEpePenLe1oEXQduhz8b0QjsqfS59QP4AJDQ==} engines: {node: '>=6.0.0'} http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - http-proxy-middleware@2.0.10: - resolution: {integrity: sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==} + http-proxy-middleware@2.0.9: + resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==} engines: {node: '>=12.0.0'} peerDependencies: '@types/express': ^4.17.13 @@ -5967,9 +6256,9 @@ packages: '@types/express': optional: true - http-proxy-middleware@4.2.0: - resolution: {integrity: sha512-ZA+oNOoM+GLoFTIzhkJptVQov73Srep2LBqhF8hG8CIPKO3nam1jonXVQ/QUH8RbwsmaaVz2SOJdzBNBHNtKbw==} - engines: {node: ^22.15.0 || ^24.0.0 || >=26.0.0} + http-proxy-middleware@3.0.5: + resolution: {integrity: sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} http-proxy@1.18.1: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} @@ -5991,13 +6280,6 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - https-proxy-agent@9.1.0: - resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} - engines: {node: '>= 20'} - - httpxy@0.5.5: - resolution: {integrity: sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6019,8 +6301,8 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} icss-utils@5.1.0: @@ -6035,12 +6317,16 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@8.0.0: + resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} + engines: {node: ^20.17.0 || >=22.9.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} image-size@0.5.5: @@ -6054,8 +6340,8 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immutable@5.1.9: - resolution: {integrity: sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -6072,9 +6358,6 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6083,6 +6366,9 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -6097,9 +6383,12 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} - install-artifact-from-github@1.7.0: - resolution: {integrity: sha512-qAb91yAKVF9rFY4rVP21ZtYUyCScxAFt9udwzVWNLBE1pQcdQeB2gd1HlNPcQNYCzCDvJ/QJQPuWQ6aTmSlU8g==} - engines: {node: '>=18'} + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + install-artifact-from-github@1.4.0: + resolution: {integrity: sha512-+y6WywKZREw5rq7U2jvr2nmZpT7cbWbQQ0N/qfcseYnzHFz2cZz1Et52oY+XttYuYeTkI8Y+R2JNWj68MpQFSg==} hasBin: true internal-slot@1.1.0: @@ -6109,20 +6398,24 @@ packages: intl-messageformat@10.7.18: resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - ip-address@10.2.0: - resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} - ip-regex@2.1.0: - resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==} - engines: {node: '>=4'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.4.0: - resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-alphabetical@1.0.4: @@ -6154,6 +6447,9 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} @@ -6166,8 +6462,8 @@ packages: resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} hasBin: true - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -6191,10 +6487,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - is-document.all@1.0.0: - resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -6257,8 +6549,8 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} - is-network-error@1.3.2: - resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} engines: {node: '>=16'} is-node-process@1.2.0: @@ -6272,6 +6564,10 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} + is-number@2.1.0: + resolution: {integrity: sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==} + engines: {node: '>=0.10.0'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -6304,10 +6600,6 @@ packages: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -6385,9 +6677,8 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} is-wsl@1.1.0: resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} @@ -6404,8 +6695,8 @@ packages: is-yarn-global@0.3.0: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} - is2@2.0.1: - resolution: {integrity: sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==} + is2@2.0.9: + resolution: {integrity: sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==} engines: {node: '>=v0.10.0'} is@3.3.2: @@ -6428,6 +6719,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + isexe@4.0.0: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} @@ -6481,14 +6776,22 @@ packages: resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} engines: {node: 20 || >=22} + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + jasmine-core@2.8.0: resolution: {integrity: sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==} jasmine-core@4.6.1: resolution: {integrity: sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==} - jasmine-core@6.3.0: - resolution: {integrity: sha512-eMm5qBovNjNoGOcgE/W207+wrcK5zrQv0Rg/rWGboUJUmZp0dFCpHTyjpuDAfCwRCqg7f9U2q2jtv/aUuzdCQg==} + jasmine-core@6.0.0: + resolution: {integrity: sha512-fmBb8aruz2mEIDBUGWOGNmxhXwFJs44SSzwbMcBDqQnNChavPTq3Ra9A6WAn6WdxvxWEdakKTcEb3NzeR3yD1A==} + + jasmine-core@6.0.1: + resolution: {integrity: sha512-gUtzV5ASR0MLBwDNqri4kBsgKNCcRQd9qOlNw/w/deavD0cl3JmWXXfH8JhKM4LTg6LPTt2IOQ4px3YYfgh2Xg==} jasmine-reporters@2.5.2: resolution: {integrity: sha512-qdewRUuFOSiWhiyWZX8Yx3YNQ9JG51ntBEO4ekLQRpktxFTwUHy24a86zD/Oi2BRTKksEdfWQZcQFqzjqIkPig==} @@ -6500,8 +6803,8 @@ packages: resolution: {integrity: sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==} hasBin: true - jasmine@6.3.0: - resolution: {integrity: sha512-u6L7yYtrtS1JALlp7f4k7Wz7o7ZKXauSKKkXc0L3qUkKrdaxYvNiMHhHp5gtTuVZZXVihXRxS7bWwDBX1wJ7EQ==} + jasmine@6.0.0: + resolution: {integrity: sha512-eSPL6LPWT39WwvHSEEbRXuSvioXMTheNhIPaeUT1OPmSprDZwj4S29884DkTx6/tyiOWTWB1N+LdW2ZSg74aEA==} hasBin: true jasminewd2@2.2.0: @@ -6512,8 +6815,8 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true jju@1.4.0: @@ -6522,8 +6825,8 @@ packages: join-path@1.1.1: resolution: {integrity: sha512-jnt9OC34sLXMLJ6YfPQ2ZEKrR9mB5ZbSnQb4LPaOx1c5rTzxpR33L18jjp0r75mGGTJmsil3qwN1B5IBeTnSSA==} - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} jpeg-js@0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} @@ -6532,18 +6835,15 @@ packages: resolution: {integrity: sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==} engines: {node: '>=12'} - js-tokens@10.0.0: - resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.15.0: - resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsbn@0.1.1: @@ -6566,6 +6866,10 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-parse-even-better-errors@5.0.0: + resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} + engines: {node: ^20.17.0 || >=22.9.0} + json-parse-helpfulerror@1.0.3: resolution: {integrity: sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==} @@ -6588,9 +6892,6 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json-with-bigint@3.5.10: - resolution: {integrity: sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -6602,8 +6903,12 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} jsonwebtoken@9.0.3: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} @@ -6666,6 +6971,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kind-of@3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -6680,8 +6989,8 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - launch-editor@2.14.1: - resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} @@ -6690,11 +6999,11 @@ packages: legacy-javascript@0.0.1: resolution: {integrity: sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg==} - less-loader@13.0.0: - resolution: {integrity: sha512-TIa8d6znKH634Mg+7OU3jevZT6KeOhh0amW+YeMPD0GM9buUn5Y7HvtyCR5pUDdLaFfqLA8AX5PTSIHMNSexEA==} - engines: {node: '>= 22.11.0'} + less-loader@12.3.0: + resolution: {integrity: sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==} + engines: {node: '>= 18.12.0'} peerDependencies: - '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0 + '@rspack/core': 0.x || 1.x less: ^3.5.0 || ^4.0.0 webpack: ^5.0.0 peerDependenciesMeta: @@ -6703,11 +7012,15 @@ packages: webpack: optional: true - less@4.6.7: - resolution: {integrity: sha512-o3UxHBPPVY1HtCXx15/z1NlknQiWyafRNbtLEv+6xFaDRI2g2xPKIH43do9dSwt8bGLTsjNSaifa48N3d6odsQ==} - engines: {node: '>=18'} + less@4.4.2: + resolution: {integrity: sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==} + engines: {node: '>=14'} hasBin: true + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} @@ -6739,102 +7052,28 @@ packages: lighthouse-stack-packs@1.12.3: resolution: {integrity: sha512-d8IsOpE83kbANgnM+Tp8+x6HcMpX9o2ITBiUERssgzAIFdZCQzs/f4k6D0DLQTE59enml9mbAOU52Wu35exWtg==} - lighthouse@13.4.0: - resolution: {integrity: sha512-QumN5pLuQvLLGWpIeUe7+LBH7oHgd0BdD3wIIncVTXe+5wiLBiO2E7pQjNDUWu1si1r0iPKtXOPTZ0sUPXHivA==} + lighthouse@13.0.3: + resolution: {integrity: sha512-mEHAQV3nn4fB+3FDapye+KGeeE4V8FERgbCFegKT7HxqDVGWVOM/BoH9Qof71fzVYVMLIiGnDsnWRrH0sQ9o4Q==} engines: {node: '>=22.19'} hasBin: true - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - listr2@10.2.2: - resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==} - engines: {node: '>=22.13.0'} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} - lmdb@3.5.6: - resolution: {integrity: sha512-j3uE8ReKNyUWDjhfEFSJqE/1DLtfTR5Z8yFzVHvBjAk37wNg7HdScjcv8ttPHRvrdgPQMPWxFFI0SsdBzI5lBw==} + lmdb@3.5.1: + resolution: {integrity: sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==} hasBin: true load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} - loader-runner@4.3.2: - resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -6853,8 +7092,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.18.1: - resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} lodash._objecttypes@2.4.1: resolution: {integrity: sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==} @@ -6892,14 +7131,17 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.mapvalues@4.6.0: + resolution: {integrity: sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==} + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - lodash.template@4.18.1: - resolution: {integrity: sha512-5urZrLnV/VD6zHK5KsVtZgt7H19v51mIzoS0aBNH8yp3I8tbswrEjOABOPY8m8uB7NuibubLrMX+Y0PXsU9X+w==} + lodash.template@4.5.0: + resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. lodash.templatesettings@4.2.0: @@ -6908,8 +7150,8 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -6946,8 +7188,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.5.2: - resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -6961,9 +7203,8 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lsofi@2.0.0: - resolution: {integrity: sha512-XTFlOBHeuXnUGuUQI0kBb4O4oQW7qCV7MRGQja1xNpxgrI/jptlly+/5126RiRold1zgyxY520qOZUZ31V0I2A==} - engines: {node: '>=20.0.0'} + lsofi@1.0.0: + resolution: {integrity: sha512-MKr9vM1MSm+TSKfI05IYxpKV1NCxpJaBLnELyIf784zYJ5KV9lGCE1EvpA2DtXDNM3fCuFeCwXUzim/fyQRi+A==} luxon@3.7.2: resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} @@ -6972,6 +7213,10 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -6980,13 +7225,13 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-dir@5.1.0: - resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==} - engines: {node: '>=18'} - make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + make-fetch-happen@15.0.3: + resolution: {integrity: sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==} + engines: {node: ^20.17.0 || >=22.9.0} + map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -6995,6 +7240,9 @@ packages: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} + map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + marked-terminal@7.3.0: resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} engines: {node: '>=16.0.0'} @@ -7011,8 +7259,8 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@18.0.6: - resolution: {integrity: sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w==} + marked@17.0.2: + resolution: {integrity: sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==} engines: {node: '>= 20'} hasBin: true @@ -7034,8 +7282,8 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memfs@4.64.0: - resolution: {integrity: sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==} + memfs@4.56.10: + resolution: {integrity: sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==} peerDependencies: tslib: '2' @@ -7043,6 +7291,10 @@ packages: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + meow@9.0.0: resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} engines: {node: '>=10'} @@ -7107,8 +7359,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - mini-css-extract-plugin@2.10.2: - resolution: {integrity: sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==} + mini-css-extract-plugin@2.10.0: + resolution: {integrity: sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -7116,30 +7368,34 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@10.2.0: + resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} + engines: {node: 20 || >=22} minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} - minimatch@6.2.3: - resolution: {integrity: sha512-5rvZbDy5y2k40rre/0OBbYnl03en25XPU3gOVO7532beGMjAipq88VdS9OeLOZNrD+Tb0lDhBJHZ7Gcd8qKlPg==} + minimatch@6.2.0: + resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} engines: {node: '>=10'} - minimatch@7.4.9: - resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==} + minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} engines: {node: '>=10'} - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} minimist-options@4.1.0: @@ -7149,51 +7405,32 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minimizer-webpack-plugin@5.6.1: - resolution: {integrity: sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@minify-html/node': - optional: true - '@swc/core': - optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@5.0.1: + resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} + engines: {node: ^20.17.0 || >=22.9.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} + engines: {node: '>=8'} - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} minizlib@3.1.0: @@ -7212,27 +7449,19 @@ packages: engines: {node: '>=10'} hasBin: true - modern-tar@0.7.6: - resolution: {integrity: sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==} - engines: {node: '>=18.0.0'} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - moo@0.5.3: - resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} + moo@0.5.2: + resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} morgan@1.10.1: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} - morgan@1.11.0: - resolution: {integrity: sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==} - engines: {node: '>= 0.8.0'} - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -7246,20 +7475,20 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpackr-extract@3.0.4: - resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.12.1: - resolution: {integrity: sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==} + msgpackr@1.11.8: + resolution: {integrity: sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==} multicast-dns@7.2.5: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true - multimatch@8.0.0: - resolution: {integrity: sha512-0D10M2/MnEyvoog7tmozlpSqL3HEU1evxUFa3v1dsKYmBDFSP1dLSX4CH2rNjpQ+4Fps8GKmUkCwiKryaKqd9A==} - engines: {node: '>=20'} + multimatch@7.0.0: + resolution: {integrity: sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==} + engines: {node: '>=18'} mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} @@ -7272,11 +7501,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.28.0: - resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==} + nan@2.25.0: + resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -7284,8 +7513,8 @@ packages: resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} hasBin: true - needle@3.5.0: - resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==} + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} hasBin: true @@ -7304,8 +7533,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - netmask@2.1.1: - resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} nice-try@1.0.5: @@ -7314,8 +7543,8 @@ packages: no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} - nock@14.0.16: - resolution: {integrity: sha512-8r4KEc6nT1D/fdLD/R1BO1CPaVEL8o40u/guFRJlXabN7vr3RmMqyjsY5Krt0nMwhsOAwXQ/mtN5vy5Jh3aErg==} + nock@14.0.10: + resolution: {integrity: sha512-Q7HjkpyPeLa0ZVZC5qpxBt5EyLczFJ91MEewQiIi9taWuA0KB/MDJlUWtON+7dGouVdADTQsf9RA7TZk6D8VMw==} engines: {node: '>=18.20.0 <20 || >=20.12.1'} node-addon-api@6.1.0: @@ -7354,24 +7583,23 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@13.0.1: - resolution: {integrity: sha512-piOr0S10qy5THB+q5BdqkoOx65XL/tjTMUAit3vciPNp+snTOBnGunWH1Rz7XZUxf2T9uFrfT/Ty4+aC3yPeyg==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + node-gyp@12.2.0: + resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} - engines: {node: '>=18'} - - nopt@10.0.1: - resolution: {integrity: sha512-df3sBr/6ax9hSGuC3CspvLlbnX8cP5L5nZwXF8cGN8l0zSWR6BvzmQ6jPUKjvo6+/xdpkNvEcucBNUdBeeV13g==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - hasBin: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} nopt@3.0.6: resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} hasBin: true + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -7383,9 +7611,49 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-package-arg@14.0.0: - resolution: {integrity: sha512-69XQh3k+dtGa1p+7RaR57IuG3rCko96xr/nUfN4yDYBXbTYICiWcOpsFKLN2GtGE9cyIljE+f1exnaYt9MvM+Q==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + npm-bundled@5.0.0: + resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-install-checks@6.3.0: + resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-install-checks@8.0.0: + resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-package-arg@13.0.2: + resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-packlist@10.0.3: + resolution: {integrity: sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@11.0.3: + resolution: {integrity: sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@9.1.0: + resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-registry-fetch@19.1.1: + resolution: {integrity: sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==} + engines: {node: ^20.17.0 || >=22.9.0} npm-run-all@4.1.5: resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} @@ -7439,10 +7707,6 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} - engines: {node: '>=12.20.0'} - on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -7500,8 +7764,8 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - ora@9.4.1: - resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} + ora@9.2.0: + resolution: {integrity: sha512-4sGT6oNDbqIuciDfD2aCkoPgHLmOe+g+xpFK2WDO0aQuD/bNHNwfdFosWP+DXmcxRyXeF8vnki6kXnOOHTuRSA==} engines: {node: '>=20'} ordered-binary@1.6.1: @@ -7542,14 +7806,14 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + p-queue@6.6.2: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - p-retry@6.2.1: resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} engines: {node: '>=16.17'} @@ -7577,6 +7841,11 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pacote@21.1.0: + resolution: {integrity: sha512-WF/PwrImIIVaLmtuCeO5L7n6DA0ZGCqmDPO/XbNjZgNUX+2O5z4f4Wdmu6erBWNICkl3ftKJvit2eIVcpegRRw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -7599,8 +7868,8 @@ packages: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} - parse5-html-rewriting-stream@8.0.1: - resolution: {integrity: sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==} + parse5-html-rewriting-stream@8.0.0: + resolution: {integrity: sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==} parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -7614,8 +7883,8 @@ packages: parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - parse5@8.0.1: - resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -7657,18 +7926,18 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} - path-to-regexp@0.1.13: - resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} path-to-regexp@1.9.0: resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -7678,14 +7947,20 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - pg-cloudflare@1.4.0: - resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - pg-connection-string@2.14.0: - resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + pg-connection-string@2.11.0: + resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} pg-gateway@0.3.0-beta.4: resolution: {integrity: sha512-CTjsM7Z+0Nx2/dyZ6r8zRsc3f9FScoD5UAOlfUx1Fdv/JOIWvRbF7gou6l6vP+uypXQVoYPgw8xZDXgMGvBa4Q==} @@ -7694,20 +7969,20 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.14.0: - resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + pg-pool@3.11.0: + resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} peerDependencies: pg: '>=8.0' - pg-protocol@1.15.0: - resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} + pg-protocol@1.11.0: + resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.22.0: - resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==} + pg@8.18.0: + resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -7721,12 +7996,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} pidtree@0.3.1: @@ -7742,6 +8017,10 @@ packages: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pinkie-promise@2.0.1: resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} engines: {node: '>=0.10.0'} @@ -7750,16 +8029,16 @@ packages: resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} engines: {node: '>=0.10.0'} - piscina@5.2.0: - resolution: {integrity: sha512-DszUCKeVN/5G5QKo6jAVHL8fmKnkJvQ0ACiVgY7YGCq3TUB2oznAOayvZPIAdEThvhczkXR+qm3IHsNXpFCYfA==} + piscina@5.1.4: + resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==} engines: {node: '>=20.x'} pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - pkijs@3.4.0: - resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} + pkijs@3.3.3: + resolution: {integrity: sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==} engines: {node: '>=16.0.0'} portfinder@1.0.38: @@ -7770,11 +8049,11 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-loader@8.2.1: - resolution: {integrity: sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==} + postcss-loader@8.2.0: + resolution: {integrity: sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==} engines: {node: '>= 18.12.0'} peerDependencies: - '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0 + '@rspack/core': 0.x || 1.x postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 peerDependenciesMeta: @@ -7831,23 +8110,19 @@ packages: peerDependencies: postcss: ^8.4.29 - postcss-selector-parser@6.1.4: - resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.4: - resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.16: - resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.19: - resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7874,14 +8149,18 @@ packages: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} - prettier@3.9.5: - resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true - proc-log@7.0.0: - resolution: {integrity: sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -7897,6 +8176,10 @@ packages: promise-breaker@6.0.0: resolution: {integrity: sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==} + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -7915,8 +8198,12 @@ packages: resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} engines: {node: '>=18'} - protobufjs@8.7.1: - resolution: {integrity: sha512-agdGHrXNTv0IrYscJPDou/PlEJk1c/hBZ9o/B5NH2i/nSPtPqacNxzgwf1CebXxFMjMrZH5sqv9uQuw96aGt/A==} + protobufjs@7.4.0: + resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==} + engines: {node: '>=12.0.0'} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} protractor@7.0.0: @@ -7929,15 +8216,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-agent-negotiate@1.1.0: - resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} - engines: {node: '>= 20'} - peerDependencies: - kerberos: ^2.0.0 - peerDependenciesMeta: - kerberos: - optional: true - proxy-agent@6.5.0: resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} @@ -7948,12 +8226,20 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} @@ -7968,9 +8254,9 @@ packages: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} - puppeteer-core@25.3.0: - resolution: {integrity: sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==} - engines: {node: '>=22.12.0'} + puppeteer-core@24.37.3: + resolution: {integrity: sha512-fokQ8gv+hNgsRWqVuP5rUjGp+wzV5aMTP3fcm8ekNabmLGlJdFHas1OdMscAH9Gzq4Qcf7cfI/Pe6wEcAqQhqg==} + engines: {node: '>=18'} pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} @@ -7999,8 +8285,12 @@ packages: resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==} engines: {node: '>=0.9'} - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} qs@6.5.5: @@ -8021,14 +8311,13 @@ packages: resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} engines: {node: '>=0.12'} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - range-parser@1.3.0: - resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} - engines: {node: '>= 0.6'} - raw-body@2.5.3: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} @@ -8041,12 +8330,8 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - re2@1.26.0: - resolution: {integrity: sha512-QTEfFp0nu1eT/HKKZC+2BKM9Lb8uqprGkcHhro9r3giiKS+1SnNZBZ1m7C7Axi/VR1YGXiu3mp35UA53yHAuNw==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - - re2js@0.4.3: - resolution: {integrity: sha512-EuNmh7jurhHEE8Ge/lBo9JuMLb3qf866Xjjfyovw3wPc7+hlqDkZq4LwhrCQMEI+ARWfrKrHozEndzlpNT0WDg==} + re2@1.23.3: + resolution: {integrity: sha512-5jh686rmj/8dYpBo72XYgwzgG8Y9HNDATYZ1x01gqZ6FvXVUP33VZ0+6GLCeavaNywz3OkXBU8iNX7LjiuisPg==} read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -8078,6 +8363,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -8122,8 +8411,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.2: - resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true rehype-parse@6.0.2: @@ -8159,6 +8448,11 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + requirejs@2.3.8: + resolution: {integrity: sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==} + engines: {node: '>=0.4.0'} + hasBin: true + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -8170,6 +8464,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-url-loader@5.0.0: resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} engines: {node: '>=12'} @@ -8177,8 +8474,8 @@ packages: resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true @@ -8194,10 +8491,14 @@ packages: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} - retry-request@8.0.3: - resolution: {integrity: sha512-qqoc4kkGgP9cmQDWELlOpAmfgJOg0Yi7MT82ZjiPWu451ayju4itwomjM4/dBEliify8C1b3tSaeCOldugtwPQ==} + retry-request@8.0.2: + resolution: {integrity: sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==} engines: {node: '>=18'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -8227,21 +8528,21 @@ packages: resolution: {integrity: sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==} engines: {node: '>=10.0.0'} - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + rolldown@1.0.0-rc.3: + resolution: {integrity: sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup-plugin-dts@6.4.1: - resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} - engines: {node: '>=20'} + rollup-plugin-dts@6.3.0: + resolution: {integrity: sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==} + engines: {node: '>=16'} peerDependencies: rollup: ^3.29.4 || ^4 - typescript: 6.0.2 + typescript: 5.9.2 - rollup-plugin-sourcemaps2@0.5.8: - resolution: {integrity: sha512-c5BfiVbKAmaz+RoEhFF44Tg/X2QoLxEB8F7wS/eNWDJF/NKxi0SF7srN3Dg0RUnIcYLyCOwscket5Mr3BImMbg==} - engines: {node: '>=22.13.0'} + rollup-plugin-sourcemaps2@0.5.4: + resolution: {integrity: sha512-XK6ITvEsKtUFN1GQbYKoqilwh1yKxTS9BLaFlVsm0IaYUYe3eVnhBWzKP4AHbkBO2BNOheGNlf407K7wCj6Rrw==} + engines: {node: '>=18.0.0'} peerDependencies: '@types/node': '>=18.0.0' rollup: '>=4' @@ -8249,8 +8550,8 @@ packages: '@types/node': optional: true - rollup@4.62.2: - resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8273,7 +8574,7 @@ packages: resolution: {integrity: sha512-PKNG0IxB+44S1HNERnagF5bqegs9sSUpY08g+jeRb2Gq36Qt5nKdVt6ZnMilfQxKgjFqd50iHvn0yairQ/pLKQ==} peerDependencies: tslint: ^5.0.0 || ^6.0.0 - typescript: 6.0.2 + typescript: 5.9.2 rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} @@ -8282,8 +8583,8 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -8310,17 +8611,20 @@ packages: safevalues@1.2.0: resolution: {integrity: sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw==} - sass-loader@17.0.0: - resolution: {integrity: sha512-0Ybm8ohBQ9LcrycVrFQp/KQBNX5a3Wda9/smS0mE/xLffzEnwvV8nykOzrbiSWNzTE3IB/jiXx8O4QmDPG2+Gw==} - engines: {node: '>= 22.11.0'} + sass-loader@16.0.6: + resolution: {integrity: sha512-sglGzId5gmlfxNs4gK2U3h7HlVRfx278YK6Ono5lwzuvi1jxig80YiuHkaDBVsYIKFhx8wN7XSCI0M2IDS/3qA==} + engines: {node: '>= 18.12.0'} peerDependencies: - '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0 + '@rspack/core': 0.x || 1.x + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 sass: ^1.3.0 sass-embedded: '*' webpack: ^5.0.0 peerDependenciesMeta: '@rspack/core': optional: true + node-sass: + optional: true sass: optional: true sass-embedded: @@ -8328,16 +8632,21 @@ packages: webpack: optional: true - sass@1.101.0: - resolution: {integrity: sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==} - engines: {node: '>=20.19.0'} + sass@1.97.2: + resolution: {integrity: sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==} + engines: {node: '>=14.0.0'} + hasBin: true + + sass@1.97.3: + resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==} + engines: {node: '>=14.0.0'} hasBin: true saucelabs@1.5.0: resolution: {integrity: sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==} - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} schema-utils@4.3.3: @@ -8367,8 +8676,13 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -8387,9 +8701,8 @@ packages: sentence-case@2.1.1: resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==} - serialize-javascript@7.0.7: - resolution: {integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==} - engines: {node: '>=20.0.0'} + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} serve-index@1.9.2: resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} @@ -8448,8 +8761,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.10.0: - resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} shelljs@0.10.0: @@ -8459,8 +8772,8 @@ packages: shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -8471,8 +8784,8 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} signal-exit@3.0.7: @@ -8482,6 +8795,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sigstore@4.1.0: + resolution: {integrity: sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==} + engines: {node: ^20.17.0 || >=22.9.0} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8501,12 +8818,8 @@ packages: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} - slice-ansi@8.0.0: - resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} - engines: {node: '>=20'} - - slugify@1.6.9: - resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} + slugify@1.6.6: + resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} engines: {node: '>=8.0.0'} smart-buffer@4.2.0: @@ -8516,11 +8829,11 @@ packages: snake-case@2.1.0: resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==} - socket.io-adapter@2.5.8: - resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==} + socket.io-adapter@2.5.6: + resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} - socket.io-parser@4.2.6: - resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} + socket.io-parser@4.2.5: + resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} engines: {node: '>=10.0.0'} socket.io@4.8.3: @@ -8534,10 +8847,13 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.9: - resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sort-any@2.0.0: + resolution: {integrity: sha512-T9JoiDewQEmWcnmPn/s9h/PH9t3d/LSWi0RgVmXSuDYeZXTZOZ1/wrK2PHaptuR1VXe3clLLt0pD6sgVOwjNEA==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -8582,8 +8898,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} spdx-license-list@2.1.0: resolution: {integrity: sha512-5PXVk9n4XDEzXOOxggv0SK6HmIaoGG28InH2qfFpURP/hUf/iwLXUxJMBg0flQLNlnHsgH6H1GPwNH55KOkqzA==} @@ -8610,11 +8926,14 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.8.2: - resolution: {integrity: sha512-kTYRg5FIcvsDtYUG2Qn9pYT6xKwiLJN5TTIvc5Mur6hIg4pSfdpHu8Yyu5bqESLHnVM3mXzD446cb2+uEaKZXg==} + sql-formatter@15.7.1: + resolution: {integrity: sha512-O8h51MnuQAnsInO7is6VoBGeB9wFvXhiTi4AqY5yGWUdF7fNcEDA6F67AAD4OvV4Seb95bPXaAPmnSii/mModg==} hasBin: true sshpk@1.18.0: @@ -8626,6 +8945,10 @@ packages: resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} + engines: {node: ^20.17.0 || >=22.9.0} + stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -8637,8 +8960,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stdin-discarder@0.3.2: - resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + stdin-discarder@0.3.1: + resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} engines: {node: '>=18'} stop-iteration-iterator@1.1.0: @@ -8652,6 +8975,9 @@ packages: stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} @@ -8665,8 +8991,8 @@ packages: resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} engines: {node: '>=8.0'} - streamx@2.28.0: - resolution: {integrity: sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -8683,20 +9009,20 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + string-width@8.1.1: + resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} string.prototype.padend@3.1.6: resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} engines: {node: '>= 0.4'} - string.prototype.trim@1.2.11: - resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.10: - resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: @@ -8720,8 +9046,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -8812,34 +9138,45 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tar-stream@3.2.0: - resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} - tar@7.5.20: - resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + tar@7.5.9: + resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} - tcp-port-used@1.0.3: - resolution: {integrity: sha512-4CEQ3qRJYo+mtEbJ+OoQu3dF4TDkwaO3RDVC4UzP5cpAOIUWwuwPjD7sdxDFFqsMUjsXVVYBMlg/boAaloThMA==} + tcp-port-used@1.0.2: + resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} - teeny-request@10.1.3: - resolution: {integrity: sha512-5yDliI1uWkYPo7W+Zvrxg6YmoWuj5iC5EydewqrRTvc68nyMTZhlPPlLg6cptUGfbQAb+N9XDPDPzF6N081lug==} + teeny-request@10.1.0: + resolution: {integrity: sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==} engines: {node: '>=18'} - teex@1.0.1: - resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} - engines: {node: '>=10'} - hasBin: true + terser-webpack-plugin@5.3.16: + resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true - terser@5.49.0: - resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} engines: {node: '>=10'} hasBin: true @@ -8856,14 +9193,17 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thingies@2.6.0: - resolution: {integrity: sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==} + thingies@2.5.0: + resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} engines: {node: '>=10.18'} peerDependencies: tslib: ^2 - third-party-web@0.29.2: - resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==} + third-party-web@0.27.0: + resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==} + + third-party-web@0.29.0: + resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==} through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} @@ -8871,6 +9211,9 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} @@ -8878,34 +9221,34 @@ packages: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} - tldts-core@7.4.8: - resolution: {integrity: sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==} + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} - tldts-icann@7.4.8: - resolution: {integrity: sha512-yI0KrSFXQSbb0FOS2HsZaoc2NzjV3c0VXM8124T3v2/sE9TggS/3KsjFsmls6RSBgSgFSQyH3CdzdgOLnJb8pA==} + tldts-icann@7.0.23: + resolution: {integrity: sha512-LMc6V1KOHFjKDU8wyDsIEJdV8o2bpc2OaYw2NxncJB2oZxJMPpiNVAbiu1HnqsUy81fkK1QWwFztVqY81hUFEg==} tmp@0.0.30: resolution: {integrity: sha512-HXdTB7lvMwcb55XFfrTM8CPr/IYREk4hVBFaQ4b/6nInrluSL86hfHm7vu0luYKCfyBZp2trCjpc8caC3vVM3w==} engines: {node: '>=0.4.0'} - tmp@0.2.7: - resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toad-cache@3.7.4: - resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} - engines: {node: '>=20'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -8949,7 +9292,7 @@ packages: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' '@types/node': '*' - typescript: 6.0.2 + typescript: 5.9.2 peerDependenciesMeta: '@swc/core': optional: true @@ -8962,7 +9305,12 @@ packages: peerDependencies: '@bazel/bazelisk': '>=1.7.5' '@bazel/concatjs': '>=5.3.0' - typescript: 6.0.2 + typescript: 5.9.2 + + tsickle@0.46.3: + resolution: {integrity: sha512-9PDXxOrtn2AdpvDin6FLGveXVGg8ec3ga8fh8mPR5lz9KtitW6riVgxgagicdfF1rgiBxDeH+5hVowPXhmZbYQ==} + peerDependencies: + typescript: 5.9.2 tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -8976,7 +9324,7 @@ packages: deprecated: TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information. hasBin: true peerDependencies: - typescript: 6.0.2 + typescript: 5.9.2 tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} @@ -8987,21 +9335,21 @@ packages: hasBin: true peerDependencies: tsutils: ^3.0.0 - typescript: 6.0.2 + typescript: 5.9.2 tsutils@2.29.0: resolution: {integrity: sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==} peerDependencies: - typescript: 6.0.2 + typescript: 5.9.2 tsutils@3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: - typescript: 6.0.2 + typescript: 5.9.2 - tsx@4.23.1: - resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} hasBin: true @@ -9009,6 +9357,10 @@ packages: resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} engines: {node: '>= 6.0.0'} + tuf-js@4.1.0: + resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} + engines: {node: ^20.17.0 || >=22.9.0} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -9047,9 +9399,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -9063,8 +9415,8 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.8: - resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} typed-assert@1.0.9: @@ -9073,14 +9425,14 @@ packages: typed-graphqlify@3.1.6: resolution: {integrity: sha512-Snlg1ZrokbkQuemOb4xjWWCJrNcOMeb2Ii0/BwMfwLCcJVNjygyqhrFkrYNvi4gDrwWFrGE0TvxxM+Slym2JMg==} - typed-query-selector@2.12.2: - resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==} + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true @@ -9103,16 +9455,16 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@6.27.0: - resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} - undici@8.7.0: - resolution: {integrity: sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ==} - engines: {node: '>=22.19.0'} + undici@7.20.0: + resolution: {integrity: sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==} + engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -9137,6 +9489,14 @@ packages: unified@7.1.0: resolution: {integrity: sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==} + unique-filename@5.0.0: + resolution: {integrity: sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==} + engines: {node: ^20.17.0 || >=22.9.0} + + unique-slug@6.0.0: + resolution: {integrity: sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==} + engines: {node: ^20.17.0 || >=22.9.0} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -9150,9 +9510,9 @@ packages: unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - universal-analytics@0.5.4: - resolution: {integrity: sha512-db38BYsx+oZEx0bc+PeGmIwG+YiYH5Xluhxf9EBnL39U4+DAXJtXQHLJ+zzTH36lx/N54/WKQQYnh0kQWJ74yg==} - engines: {node: '>=22.0.0'} + universal-analytics@0.5.3: + resolution: {integrity: sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==} + engines: {node: '>=12.18.2'} universal-github-app-jwt@2.2.2: resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} @@ -9211,23 +9571,17 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@14.0.1: - resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} - hasBin: true - uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -9242,9 +9596,13 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - validate-npm-package-name@8.0.0: - resolution: {integrity: sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} validate.js@0.12.0: resolution: {integrity: sha512-/x2RJSvbqEyxKj0RPN4xaRquK+EggjeVXiDDEyrJzsJogjtiZ9ov7lj/svVb4DM5Q5braQF4cooAryQbUwOxlA==} @@ -9266,16 +9624,15 @@ packages: vfile@3.0.1: resolution: {integrity: sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==} - vite@8.1.4: - resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 - esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 + lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -9286,14 +9643,12 @@ packages: peerDependenciesMeta: '@types/node': optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true jiti: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true sass-embedded: @@ -9317,10 +9672,10 @@ packages: resolution: {integrity: sha512-pmcnJdIVziZTk1V0Cqehmh3gIabBRkBYXkv9vx+1CZDNEa41kNGUBFwQLzw21erYOd2QnD8jJeZhBGqnlT1HWw==} peerDependencies: tslint: '*' - typescript: 6.0.2 + typescript: 5.9.2 - watchpack@2.5.2: - resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} wbuf@1.7.3: @@ -9332,9 +9687,6 @@ packages: weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} - web-features@3.32.0: - resolution: {integrity: sha512-PQBbTofqV8FtMP65oT9tLPjbN4FSB2dRdNxLM0A9j4bNifVpFhEP/ATXSMMJAqPPWb/pgUOh6B+98yzfNEVbNw==} - web-namespaces@1.1.4: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} @@ -9345,8 +9697,8 @@ packages: web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} - webdriver-bidi-protocol@0.4.2: - resolution: {integrity: sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==} + webdriver-bidi-protocol@0.4.1: + resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==} webdriver-js-extender@2.1.0: resolution: {integrity: sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==} @@ -9369,17 +9721,8 @@ packages: webpack: optional: true - webpack-dev-middleware@8.0.3: - resolution: {integrity: sha512-zWrde9VZDiRaFuWsjHO40wm9LxxtXEk8DdzFXdU7eU5ZpiANnZZDBbZgN3guxbEoKqUHd9YupBmynyioz42nkA==} - engines: {node: '>= 20.9.0'} - peerDependencies: - webpack: ^5.101.0 - peerDependenciesMeta: - webpack: - optional: true - - webpack-dev-server@5.2.6: - resolution: {integrity: sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==} + webpack-dev-server@5.2.3: + resolution: {integrity: sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==} engines: {node: '>= 18.12.0'} hasBin: true peerDependencies: @@ -9395,8 +9738,8 @@ packages: resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} engines: {node: '>=18.0.0'} - webpack-sources@3.5.1: - resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack-subresource-integrity@5.1.0: @@ -9409,8 +9752,8 @@ packages: html-webpack-plugin: optional: true - webpack@5.108.4: - resolution: {integrity: sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==} + webpack@5.105.0: + resolution: {integrity: sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -9419,8 +9762,8 @@ packages: webpack-cli: optional: true - websocket-driver@0.7.5: - resolution: {integrity: sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==} + websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} websocket-extensions@0.1.4: @@ -9451,8 +9794,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.22: - resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} which@1.3.1: @@ -9469,9 +9812,14 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true - which@7.0.0: - resolution: {integrity: sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true widest-line@3.1.0: @@ -9500,10 +9848,6 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} - engines: {node: '>=20'} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -9530,8 +9874,8 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@7.5.11: - resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -9542,8 +9886,20 @@ packages: utf-8-validate: optional: true - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -9606,12 +9962,12 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -9635,18 +9991,21 @@ packages: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} - yargs@16.2.2: - resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} - yargs@17.7.3: - resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} yargs@18.0.0: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -9667,19 +10026,19 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: - zod: ^3.25.28 || ^4 + zod: ^3.25 || ^4 zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - zone.js@0.16.2: - resolution: {integrity: sha512-Eky7p2Z1Ig3NnbfodSPoARCjKBSTFMnE/ACsP1L/XJEfY4SdOFce19BsUCWVwL6K5ABZFy5J3bjcMWffX+YM3Q==} + zone.js@0.16.0: + resolution: {integrity: sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==} zx@8.8.5: resolution: {integrity: sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==} @@ -9688,204 +10047,197 @@ packages: snapshots: - '@actions/core@3.0.1': + '@actions/core@3.0.0': dependencies: '@actions/exec': 3.0.0 - '@actions/http-client': 4.0.1 + '@actions/http-client': 4.0.0 '@actions/exec@3.0.0': dependencies: '@actions/io': 3.0.2 - '@actions/http-client@4.0.1': + '@actions/http-client@4.0.0': dependencies: tunnel: 0.0.6 - undici: 6.27.0 + undici: 6.23.0 '@actions/io@3.0.2': {} - '@algolia/abtesting@1.21.2': + '@algolia/abtesting@1.13.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-abtesting@5.55.2': + '@algolia/client-abtesting@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-analytics@5.55.2': + '@algolia/client-analytics@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-common@5.55.2': {} + '@algolia/client-common@5.47.0': {} - '@algolia/client-insights@5.55.2': + '@algolia/client-insights@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-personalization@5.55.2': + '@algolia/client-personalization@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-query-suggestions@5.55.2': + '@algolia/client-query-suggestions@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/client-search@5.55.2': + '@algolia/client-search@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/ingestion@1.55.2': + '@algolia/ingestion@1.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/monitoring@1.55.2': + '@algolia/monitoring@1.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/recommend@5.55.2': + '@algolia/recommend@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + '@algolia/client-common': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 - '@algolia/requester-browser-xhr@5.55.2': + '@algolia/requester-browser-xhr@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 + '@algolia/client-common': 5.47.0 - '@algolia/requester-fetch@5.55.2': + '@algolia/requester-fetch@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 + '@algolia/client-common': 5.47.0 - '@algolia/requester-node-http@5.55.2': + '@algolia/requester-node-http@5.47.0': dependencies: - '@algolia/client-common': 5.55.2 + '@algolia/client-common': 5.47.0 '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular-devkit/architect@0.2201.0-next.4(chokidar@5.0.0)': + '@angular-devkit/architect@0.2102.0-next.2(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 22.1.0-next.4(chokidar@5.0.0) + '@angular-devkit/core': 21.2.0-next.2(chokidar@5.0.0) rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@22.1.0-next.4(2a4d79353011b4a0fecac35f3da0f711)': + '@angular-devkit/build-angular@21.2.0-next.2(27ef118a8e7055bf6f347df217a8d356)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2201.0-next.4(chokidar@5.0.0) - '@angular-devkit/build-webpack': 0.2201.0-next.4(chokidar@5.0.0)(webpack-dev-server@5.2.6(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - '@angular-devkit/core': 22.1.0-next.4(chokidar@5.0.0) - '@angular/build': 22.1.0-next.4(fdd038a6c122022683c5f117879e88a2) - '@angular/compiler-cli': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) - '@babel/core': 8.0.1 - '@babel/generator': 8.0.0 - '@babel/helper-annotate-as-pure': 8.0.0 + '@angular-devkit/architect': 0.2102.0-next.2(chokidar@5.0.0) + '@angular-devkit/build-webpack': 0.2102.0-next.2(chokidar@5.0.0)(webpack-dev-server@5.2.3(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)))(webpack@5.105.0(esbuild@0.27.2)) + '@angular-devkit/core': 21.2.0-next.2(chokidar@5.0.0) + '@angular/build': 21.2.0-next.2(8b7b30166d44cf176f98acb445f20626) + '@angular/compiler-cli': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) + '@babel/core': 7.29.0 + '@babel/generator': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/plugin-transform-async-generator-functions': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-async-to-generator': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-runtime': 8.0.1(@babel/core@8.0.1) - '@babel/preset-env': 8.0.2(@babel/core@8.0.1) - '@babel/runtime': 8.0.0 - '@discoveryjs/json-ext': 1.1.0 - '@ngtools/webpack': 22.1.0-next.4(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(typescript@6.0.2)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.0(@babel/core@7.29.0) + '@babel/runtime': 7.28.6 + '@discoveryjs/json-ext': 0.6.3 + '@ngtools/webpack': 21.2.0-next.2(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.105.0(esbuild@0.27.2)) ansi-colors: 4.1.3 - autoprefixer: 10.5.2(postcss@8.5.16) - babel-loader: 10.1.1(@babel/core@8.0.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - browserslist: 4.28.6 - copy-webpack-plugin: 14.0.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - css-loader: 7.1.4(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - esbuild-wasm: 0.28.1 - http-proxy-middleware: 4.2.0 + autoprefixer: 10.4.24(postcss@8.5.6) + babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.0(esbuild@0.27.2)) + browserslist: 4.28.1 + copy-webpack-plugin: 13.0.1(webpack@5.105.0(esbuild@0.27.2)) + css-loader: 7.1.3(webpack@5.105.0(esbuild@0.27.2)) + esbuild-wasm: 0.27.2 + http-proxy-middleware: 3.0.5 istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 - less: 4.6.7 - less-loader: 13.0.0(less@4.6.7)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - license-webpack-plugin: 4.0.2(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + less: 4.4.2 + less-loader: 12.3.0(less@4.4.2)(webpack@5.105.0(esbuild@0.27.2)) + license-webpack-plugin: 4.0.2(webpack@5.105.0(esbuild@0.27.2)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.10.2(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + mini-css-extract-plugin: 2.10.0(webpack@5.105.0(esbuild@0.27.2)) open: 11.0.0 - ora: 9.4.1 - picomatch: 4.0.5 - piscina: 5.2.0 - postcss: 8.5.16 - postcss-loader: 8.2.1(postcss@8.5.16)(typescript@6.0.2)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + ora: 9.2.0 + picomatch: 4.0.3 + piscina: 5.1.4 + postcss: 8.5.6 + postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.105.0(esbuild@0.27.2)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 - sass: 1.101.0 - sass-loader: 17.0.0(sass@1.101.0)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - semver: 7.8.5 - source-map-loader: 5.0.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + sass: 1.97.3 + sass-loader: 16.0.6(sass@1.97.3)(webpack@5.105.0(esbuild@0.27.2)) + semver: 7.7.3 + source-map-loader: 5.0.0(webpack@5.105.0(esbuild@0.27.2)) source-map-support: 0.5.21 - terser: 5.49.0 - tinyglobby: 0.2.17 + terser: 5.46.0 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 tslib: 2.8.1 - typescript: 6.0.2 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) - webpack-dev-middleware: 8.0.3(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - webpack-dev-server: 5.2.6(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + typescript: 5.9.2 + webpack: 5.105.0(esbuild@0.27.2) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) + webpack-dev-server: 5.2.3(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + webpack-subresource-integrity: 5.1.0(webpack@5.105.0(esbuild@0.27.2)) optionalDependencies: - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/localize': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) - '@angular/platform-server': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) - '@angular/ssr': 22.1.0-next.4(ae2ab38a56a690c5ea7806ad50928984) - esbuild: 0.28.1 + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/localize': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) + '@angular/platform-server': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) + '@angular/ssr': 21.2.0-next.2(732a4b7fff947bcf4560cdcda99ad9b0) + esbuild: 0.27.2 karma: 6.4.4(bufferutil@4.1.0) + protractor: 7.0.0 transitivePeerDependencies: - '@angular/compiler' - - '@minify-html/node' - '@rspack/core' - '@swc/core' - - '@swc/css' - - '@swc/html' - '@types/node' - - '@vitejs/devtools' - bufferutil - chokidar - - clean-css - - cssnano - - csso - debug - - html-minifier-terser - html-webpack-plugin - jiti - - kerberos - lightningcss - - rollup + - node-sass - sass-embedded - stylus - sugarss @@ -9897,85 +10249,84 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2201.0-next.4(chokidar@5.0.0)(webpack-dev-server@5.2.6(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)))(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16))': + '@angular-devkit/build-webpack@0.2102.0-next.2(chokidar@5.0.0)(webpack-dev-server@5.2.3(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)))(webpack@5.105.0(esbuild@0.27.2))': dependencies: - '@angular-devkit/architect': 0.2201.0-next.4(chokidar@5.0.0) + '@angular-devkit/architect': 0.2102.0-next.2(chokidar@5.0.0) rxjs: 7.8.2 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) - webpack-dev-server: 5.2.6(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + webpack: 5.105.0(esbuild@0.27.2) + webpack-dev-server: 5.2.3(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) transitivePeerDependencies: - chokidar - '@angular-devkit/core@22.1.0-next.4(chokidar@5.0.0)': + '@angular-devkit/core@21.2.0-next.2(chokidar@5.0.0)': dependencies: - ajv: 8.20.0 + ajv: 8.17.1 ajv-formats: 3.0.1 jsonc-parser: 3.3.1 - picomatch: 4.0.5 + picomatch: 4.0.3 rxjs: 7.8.2 source-map: 0.7.6 optionalDependencies: chokidar: 5.0.0 - '@angular-devkit/schematics@22.1.0-next.4(chokidar@5.0.0)': + '@angular-devkit/schematics@21.2.0-next.2(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 22.1.0-next.4(chokidar@5.0.0) + '@angular-devkit/core': 21.2.0-next.2(chokidar@5.0.0) jsonc-parser: 3.3.1 magic-string: 0.30.21 - ora: 9.4.1 + ora: 9.2.0 rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular/build@22.1.0-next.4(fdd038a6c122022683c5f117879e88a2)': + '@angular/build@21.2.0-next.2(8b7b30166d44cf176f98acb445f20626)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2201.0-next.4(chokidar@5.0.0) - '@angular/compiler': 22.1.0-next.6 - '@angular/compiler-cli': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 + '@angular-devkit/architect': 0.2102.0-next.2(chokidar@5.0.0) + '@angular/compiler': 21.2.0-next.3 + '@angular/compiler-cli': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 6.1.1(@types/node@22.20.1) - '@vitejs/plugin-basic-ssl': 2.3.0(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.7)(sass@1.101.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) - beasties: 0.4.2 - browserslist: 4.28.6 - esbuild: 0.28.1 - https-proxy-agent: 9.1.0 + '@inquirer/confirm': 5.1.21(@types/node@22.19.11) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + beasties: 0.4.1 + browserslist: 4.28.1 + esbuild: 0.27.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 - listr2: 10.2.2 + listr2: 9.0.5 magic-string: 0.30.21 mrmime: 2.0.1 - parse5-html-rewriting-stream: 8.0.1 - picomatch: 4.0.5 - piscina: 5.2.0 - rolldown: 1.1.5 - sass: 1.101.0 - semver: 7.8.5 + parse5-html-rewriting-stream: 8.0.0 + picomatch: 4.0.3 + piscina: 5.1.4 + rolldown: 1.0.0-rc.3 + sass: 1.97.3 + semver: 7.7.3 source-map-support: 0.5.21 - tinyglobby: 0.2.17 + tinyglobby: 0.2.15 tslib: 2.8.1 - typescript: 6.0.2 - vite: 8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.7)(sass@1.101.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) - watchpack: 2.5.2 + typescript: 5.9.2 + undici: 7.20.0 + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + watchpack: 2.5.1 optionalDependencies: - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/localize': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) - '@angular/platform-server': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) - '@angular/ssr': 22.1.0-next.4(ae2ab38a56a690c5ea7806ad50928984) - istanbul-lib-instrument: 6.0.3 + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/localize': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) + '@angular/platform-server': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) + '@angular/ssr': 21.2.0-next.2(732a4b7fff947bcf4560cdcda99ad9b0) karma: 6.4.4(bufferutil@4.1.0) - less: 4.6.7 - lmdb: 3.5.6 - postcss: 8.5.16 - rollup: 4.62.2 + less: 4.4.2 + lmdb: 3.5.1 + postcss: 8.5.6 transitivePeerDependencies: - '@types/node' - - '@vitejs/devtools' - chokidar - jiti - - kerberos + - lightningcss - sass-embedded - stylus - sugarss @@ -9984,95 +10335,104 @@ snapshots: - tsx - yaml - '@angular/cli@22.1.0-next.4(@types/node@22.20.1)(chokidar@5.0.0)': + '@angular/cli@21.2.0-next.2(@types/node@22.19.11)(chokidar@5.0.0)(hono@4.11.9)': dependencies: - '@angular-devkit/architect': 0.2201.0-next.4(chokidar@5.0.0) - '@angular-devkit/core': 22.1.0-next.4(chokidar@5.0.0) - '@angular-devkit/schematics': 22.1.0-next.4(chokidar@5.0.0) - '@inquirer/prompts': 8.5.2(@types/node@22.20.1) - '@listr2/prompt-adapter-inquirer': 4.2.4(@inquirer/prompts@8.5.2(@types/node@22.20.1))(@types/node@22.20.1)(listr2@10.2.2) - '@modelcontextprotocol/sdk': 1.29.0 - '@schematics/angular': 22.1.0-next.4(chokidar@5.0.0) - algoliasearch: 5.55.2 + '@angular-devkit/architect': 0.2102.0-next.2(chokidar@5.0.0) + '@angular-devkit/core': 21.2.0-next.2(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.0-next.2(chokidar@5.0.0) + '@inquirer/prompts': 7.10.1(@types/node@22.19.11) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@22.19.11))(@types/node@22.19.11)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.25.3(hono@4.11.9) + '@schematics/angular': 21.2.0-next.2(chokidar@5.0.0) + '@yarnpkg/lockfile': 1.1.0 + algoliasearch: 5.47.0 + ini: 6.0.0 jsonc-parser: 3.3.1 - listr2: 10.2.2 - npm-package-arg: 14.0.0 - parse5-html-rewriting-stream: 8.0.1 - semver: 7.8.5 + listr2: 9.0.5 + npm-package-arg: 13.0.2 + pacote: 21.1.0 + parse5-html-rewriting-stream: 8.0.0 + semver: 7.7.3 yargs: 18.0.0 - zod: 4.4.3 + zod: 4.3.6 transitivePeerDependencies: - '@cfworker/json-schema' - '@types/node' - chokidar + - hono - supports-color - '@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7)': + '@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7)': dependencies: - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' + - supports-color - '@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)': + '@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)': dependencies: - '@angular/compiler': 22.1.0-next.6 - '@babel/core': 8.0.1 + '@angular/compiler': 21.2.0-next.3 + '@babel/core': 7.29.0 '@jridgewell/sourcemap-codec': 1.5.5 chokidar: 5.0.0 convert-source-map: 1.9.0 reflect-metadata: 0.2.2 - semver: 7.8.5 + semver: 7.7.4 tslib: 2.8.1 yargs: 18.0.0 optionalDependencies: - typescript: 6.0.2 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color - '@angular/compiler@22.1.0-next.6': + '@angular/compiler@21.2.0-next.3': dependencies: tslib: 2.8.1 - '@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)': + '@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)': dependencies: rxjs: 6.6.7 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 22.1.0-next.6 - zone.js: 0.16.2 + '@angular/compiler': 21.2.0-next.3 + zone.js: 0.16.0 - '@angular/forms@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7)': + '@angular/forms@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7)': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) '@standard-schema/spec': 1.1.0 rxjs: 6.6.7 tslib: 2.8.1 - zod: 4.4.3 transitivePeerDependencies: - '@angular/compiler-cli' + - supports-color - '@angular/localize@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)': + '@angular/localize@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)': dependencies: - '@angular/compiler': 22.1.0-next.6 - '@angular/compiler-cli': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) - '@babel/core': 8.0.1 - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) - tinyglobby: 0.2.17 + '@angular/compiler': 21.2.0-next.3 + '@angular/compiler-cli': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) + '@babel/core': 7.29.0 + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) + '@types/babel__core': 7.20.5 + tinyglobby: 0.2.15 yargs: 18.0.0 + transitivePeerDependencies: + - supports-color - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/7a67d5221a9efec2b69b102759d04223a3fc8552(@modelcontextprotocol/sdk@1.29.0)': + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/869bfda5998a1298e4eb55dbc97edc059625af5a(@modelcontextprotocol/sdk@1.26.0)': dependencies: - '@actions/core': 3.0.1 - '@conventional-changelog/git-client': 3.1.0(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.0) + '@actions/core': 3.0.0 '@google-cloud/spanner': 8.0.0(supports-color@10.2.2) - '@google/genai': 2.12.0(@modelcontextprotocol/sdk@1.29.0)(bufferutil@4.1.0)(supports-color@10.2.2)(utf-8-validate@6.0.6) - '@inquirer/prompts': 8.5.2(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) - '@octokit/auth-app': 8.2.0 + '@google/genai': 1.38.0(@modelcontextprotocol/sdk@1.26.0)(bufferutil@4.1.0)(supports-color@10.2.2)(utf-8-validate@6.0.6) + '@inquirer/prompts': 8.2.0(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) + '@octokit/auth-app': 8.1.2 '@octokit/core': 7.0.6 '@octokit/graphql': 9.0.3 '@octokit/graphql-schema': 15.26.1 @@ -10082,13 +10442,14 @@ snapshots: '@octokit/request-error': 7.1.0 '@octokit/rest': 22.0.1 '@octokit/types': 16.0.0 - '@pnpm/dependency-path': 1001.1.10 + '@pnpm/dependency-path': 1001.1.9 '@types/cli-progress': 3.11.6 '@types/ejs': 3.1.5 '@types/events': 3.0.3 '@types/folder-hash': 4.0.4 + '@types/git-raw-commits': 5.0.1 '@types/jasmine': 6.0.0 - '@types/node': 24.13.3 + '@types/node': 24.10.9 '@types/semver': 7.7.1 '@types/which': 3.0.4 '@types/yargs': 17.0.35 @@ -10096,120 +10457,133 @@ snapshots: '@yarnpkg/lockfile': 1.1.0 bufferutil: 4.1.0 cli-progress: 3.12.0 - conventional-commits-filter: 6.0.1 - conventional-commits-parser: 7.1.0 - ejs: 6.0.1 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.2.1 + ejs: 4.0.1 encoding: 0.1.13 fast-glob: 3.3.3 - firebase: 12.16.0 - folder-hash: 4.1.3(supports-color@10.2.2) - jasmine: 6.3.0 - jasmine-core: 6.3.0 + firebase: 12.8.0 + folder-hash: 4.1.1(supports-color@10.2.2) + git-raw-commits: 5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) + jasmine: 6.0.0 + jasmine-core: 6.0.0 jasmine-reporters: 2.5.2 jsonc-parser: 3.3.1 - minimatch: 10.2.5 - multimatch: 8.0.0 - nock: 14.0.16 - semver: 7.8.5 + minimatch: 10.1.1 + multimatch: 7.0.0 + nock: 14.0.10 + semver: 7.7.3 supports-color: 10.2.2 - tsx: 4.23.1 + tsx: 4.21.0 typed-graphqlify: 3.1.6 - typescript: 6.0.2 + typescript: 5.9.2 utf-8-validate: 6.0.6 - which: 7.0.0 - yaml: 2.9.0 + which: 6.0.0 + yaml: 2.8.2 yargs: 18.0.0 - zod: 4.4.3 + zod: 4.3.6 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - '@react-native-async-storage/async-storage' - '@angular/platform-browser-dynamic@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))': + '@angular/platform-browser-dynamic@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/compiler': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/compiler': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) tslib: 2.8.1 - '@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))': + '@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' + - supports-color - '@angular/platform-server@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7)': + '@angular/platform-server@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7)': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/compiler': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/compiler': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 xhr2: 0.2.1 transitivePeerDependencies: - '@angular/compiler-cli' + - supports-color - '@angular/router@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7)': + '@angular/router@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7)': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/platform-browser': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)) - '@nginfra/angular-linking': 1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2)) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/platform-browser': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' + - supports-color - '@angular/ssr@22.1.0-next.4(ae2ab38a56a690c5ea7806ad50928984)': + '@angular/ssr@21.2.0-next.2(732a4b7fff947bcf4560cdcda99ad9b0)': dependencies: - '@angular/common': 22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7) - '@angular/core': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2) - '@angular/router': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + '@angular/common': 21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7) + '@angular/core': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0) + '@angular/router': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) tslib: 2.8.1 optionalDependencies: - '@angular/platform-server': 22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/compiler@22.1.0-next.6)(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(@angular/platform-browser@22.1.0-next.6(@angular/common@22.1.0-next.6(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2))(rxjs@6.6.7))(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(@angular/core@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(rxjs@6.6.7)(zone.js@0.16.2)))(rxjs@6.6.7) + '@angular/platform-server': 21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/compiler@21.2.0-next.3)(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(@angular/platform-browser@21.2.0-next.3(@angular/common@21.2.0-next.3(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0))(rxjs@6.6.7))(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(@angular/core@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(rxjs@6.6.7)(zone.js@0.16.0)))(rxjs@6.6.7) '@apidevtools/json-schema-ref-parser@9.1.2': dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 call-me-maybe: 1.0.2 - js-yaml: 4.3.0 + js-yaml: 4.1.1 + + '@apphosting/build@0.1.7(@types/node@22.19.11)(typescript@5.9.2)': + dependencies: + '@apphosting/common': 0.0.9 + '@npmcli/promise-spawn': 3.0.0 + colorette: 2.0.20 + commander: 11.1.0 + npm-pick-manifest: 9.1.0 + ts-node: 10.9.2(@types/node@22.19.11)(typescript@5.9.2) + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + - typescript '@apphosting/common@0.0.8': {} - '@babel/code-frame@7.29.7': + '@apphosting/common@0.0.9': {} + + '@babel/code-frame@7.29.0': dependencies: - '@babel/helper-validator-identifier': 7.29.7 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/code-frame@8.0.0': - dependencies: - '@babel/helper-validator-identifier': 8.0.4 - js-tokens: 10.0.0 + '@babel/compat-data@7.29.0': {} - '@babel/compat-data@7.29.7': {} - - '@babel/compat-data@8.0.0': {} - - '@babel/core@7.29.7': + '@babel/core@7.26.10': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/remapping': 2.3.5 + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.26.10) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 @@ -10218,645 +10592,686 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@8.0.1': - dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helpers': 8.0.0 - '@babel/parser': 8.0.4 - '@babel/template': 8.0.0 - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 - '@types/gensync': 1.0.5 + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - empathic: 2.0.1 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 - import-meta-resolve: 4.2.0 json5: 2.2.3 - obug: 2.1.3 - semver: 7.8.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.0': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/generator@8.0.0': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 8.0.4 - '@babel/types': 8.0.4 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@types/jsesc': 2.5.1 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@8.0.0': + '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 8.0.4 + '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.29.7': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.6 + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-compilation-targets@8.0.0': - dependencies: - '@babel/compat-data': 8.0.0 - '@babel/helper-validator-option': 8.0.0 - browserslist: 4.28.6 - lru-cache: 11.5.2 - semver: 7.8.5 - - '@babel/helper-create-class-features-plugin@8.0.1(@babel/core@8.0.1)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 - '@babel/helper-member-expression-to-functions': 8.0.0 - '@babel/helper-optimise-call-expression': 8.0.0 - '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 - '@babel/traverse': 8.0.4 - semver: 7.8.5 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@babel/helper-create-regexp-features-plugin@8.0.1(@babel/core@8.0.1)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 - semver: 7.8.5 + semver: 6.3.1 - '@babel/helper-define-polyfill-provider@1.0.0(@babel/core@8.0.1)': + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3(supports-color@10.2.2) lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-globals@8.0.0': {} - - '@babel/helper-member-expression-to-functions@8.0.0': - dependencies: - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.29.7': + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@8.0.0': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@8.0.1(@babel/core@8.0.1)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-imports': 8.0.0 - '@babel/helper-validator-identifier': 8.0.4 - '@babel/traverse': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helper-optimise-call-expression@8.0.0': + '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 8.0.4 + '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 + '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@8.0.1(@babel/core@8.0.1)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 - '@babel/helper-wrap-function': 8.0.0 - '@babel/traverse': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helper-replace-supers@8.0.1(@babel/core@8.0.1)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-member-expression-to-functions': 8.0.0 - '@babel/helper-optimise-call-expression': 8.0.0 - '@babel/traverse': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helper-skip-transparent-expression-wrappers@8.0.0': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.29.7 - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-string-parser@8.0.0': {} - - '@babel/helper-validator-identifier@7.29.7': {} + '@babel/types': 7.29.0 - '@babel/helper-validator-identifier@8.0.4': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-option@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-option@8.0.0': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@8.0.0': + '@babel/helper-wrap-function@7.28.6': dependencies: - '@babel/template': 8.0.0 - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helpers@7.29.7': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/helpers@8.0.0': + '@babel/parser@7.29.0': dependencies: - '@babel/template': 8.0.0 - '@babel/types': 8.0.4 + '@babel/types': 7.29.0 - '@babel/parser@7.29.7': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/parser@8.0.4': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/types': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/core': 7.29.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 - '@babel/plugin-transform-optional-chaining': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-remap-async-to-generator': 8.0.1(@babel/core@8.0.1) - '@babel/traverse': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-to-generator@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-imports': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-remap-async-to-generator': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-block-scoped-functions@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-block-scoping@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-static-block@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-classes@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helper-globals': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.1) - '@babel/traverse': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-computed-properties@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-destructuring@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 - '@babel/plugin-transform-dotall-regex@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-duplicate-keys@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-destructuring': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-exponentiation-operator@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-export-namespace-from@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-function-name@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-json-strings@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-literals@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-modules-systemjs@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-validator-identifier': 8.0.4 + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-modules-umd@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-new-target@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-destructuring': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-parameters': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-super@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-replace-supers': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-optional-catch-binding@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-optional-chaining@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-parameters@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-private-methods@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-property-in-object@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-annotate-as-pure': 8.0.0 - '@babel/helper-create-class-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-property-literals@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-regenerator@8.0.2(@babel/core@8.0.1)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-imports': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-shorthand-properties@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-spread@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-skip-transparent-expression-wrappers': 8.0.0 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-sticky-regex@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-template-literals@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-typeof-symbol@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-unicode-escapes@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-unicode-property-regex@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-unicode-regex@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/plugin-transform-unicode-sets-regex@8.0.1(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-create-regexp-features-plugin': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - - '@babel/preset-env@8.0.2(@babel/core@8.0.1)': - dependencies: - '@babel/compat-data': 8.0.0 - '@babel/core': 8.0.1 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/helper-validator-option': 8.0.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-arrow-functions': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-async-generator-functions': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-async-to-generator': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-block-scoped-functions': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-block-scoping': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-class-properties': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-class-static-block': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-classes': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-computed-properties': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-destructuring': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-dotall-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-duplicate-keys': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-dynamic-import': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-explicit-resource-management': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-exponentiation-operator': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-export-namespace-from': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-for-of': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-function-name': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-json-strings': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-literals': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-logical-assignment-operators': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-member-expression-literals': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-modules-amd': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-modules-commonjs': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-modules-systemjs': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-modules-umd': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-named-capturing-groups-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-new-target': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-nullish-coalescing-operator': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-numeric-separator': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-object-rest-spread': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-object-super': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-optional-catch-binding': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-optional-chaining': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-parameters': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-private-methods': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-private-property-in-object': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-property-literals': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-regenerator': 8.0.2(@babel/core@8.0.1) - '@babel/plugin-transform-regexp-modifiers': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-reserved-words': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-shorthand-properties': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-spread': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-sticky-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-template-literals': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-typeof-symbol': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-unicode-escapes': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-unicode-property-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-unicode-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-unicode-sets-regex': 8.0.1(@babel/core@8.0.1) - '@babel/preset-modules': 0.2.0(@babel/core@8.0.1) - babel-plugin-polyfill-corejs3: 1.0.0(@babel/core@8.0.1) - core-js-compat: 3.49.0 - semver: 7.8.5 - - '@babel/preset-modules@0.2.0(@babel/core@8.0.1)': - dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-dotall-regex': 8.0.1(@babel/core@8.0.1) - '@babel/plugin-transform-unicode-property-regex': 8.0.1(@babel/core@8.0.1) - '@babel/types': 8.0.4 - esutils: 2.0.3 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color - '@babel/runtime@8.0.0': {} + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@babel/template@7.29.7': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/runtime@7.28.6': {} - '@babel/template@8.0.0': + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 8.0.0 - '@babel/parser': 8.0.4 - '@babel/types': 8.0.4 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@babel/traverse@8.0.4': + '@babel/types@7.29.0': dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-globals': 8.0.0 - '@babel/parser': 8.0.4 - '@babel/template': 8.0.0 - '@babel/types': 8.0.4 - obug: 2.1.3 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@babel/types@8.0.4': - dependencies: - '@babel/helper-string-parser': 8.0.0 - '@babel/helper-validator-identifier': 8.0.4 - - '@bazel/bazelisk@1.28.1': {} + '@bazel/bazelisk@1.26.0': {} '@bazel/buildifier@8.2.1': {} @@ -10868,22 +11283,21 @@ snapshots: '@colors/colors@1.6.0': {} - '@conventional-changelog/git-client@3.1.0(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.0)': + '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1)': dependencies: - '@simple-libs/child-process-utils': 2.0.0 - '@simple-libs/stream-utils': 2.0.0 - semver: 7.8.5 + '@types/semver': 7.7.1 + semver: 7.7.4 optionalDependencies: - conventional-commits-filter: 6.0.1 - conventional-commits-parser: 7.1.0 + conventional-commits-filter: 5.0.0 + conventional-commits-parser: 6.2.1 '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.1.4)': + '@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.1.2)': dependencies: - postcss-selector-parser: 6.1.4 + postcss-selector-parser: 6.1.2 '@dabh/diagnostics@2.0.8': dependencies: @@ -10891,430 +11305,505 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@discoveryjs/json-ext@1.1.0': {} + '@discoveryjs/json-ext@0.6.3': {} - '@electric-sql/pglite-tools@0.2.21(@electric-sql/pglite@0.3.16)': + '@electric-sql/pglite-tools@0.2.20(@electric-sql/pglite@0.3.15)': dependencies: - '@electric-sql/pglite': 0.3.16 + '@electric-sql/pglite': 0.3.15 '@electric-sql/pglite@0.2.17': {} - '@electric-sql/pglite@0.3.16': {} + '@electric-sql/pglite@0.3.15': {} - '@emnapi/core@1.11.1': + '@emnapi/core@1.8.1': dependencies: - '@emnapi/wasi-threads': 1.2.2 + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.1': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.2': + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.28.1': + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/android-arm64@0.28.1': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/android-arm@0.28.1': + '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/android-x64@0.28.1': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/darwin-arm64@0.28.1': + '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/darwin-x64@0.28.1': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': + '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.28.1': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-arm64@0.28.1': + '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-arm@0.28.1': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-ia32@0.28.1': + '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-loong64@0.28.1': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.28.1': + '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-ppc64@0.28.1': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/win32-ia32@0.28.1': + '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/win32-x64@0.28.1': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@firebase/ai@2.13.1(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@firebase/ai@2.7.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/app-check-interop-types': 0.3.4 - '@firebase/app-types': 0.9.5 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/app-check-interop-types': 0.3.3 + '@firebase/app-types': 0.9.3 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/analytics-compat@0.2.28(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/analytics-compat@0.2.25(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/analytics': 0.10.22(@firebase/app@0.15.1) - '@firebase/analytics-types': 0.8.4 - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/analytics': 0.10.19(@firebase/app@0.14.7) + '@firebase/analytics-types': 0.8.3 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/analytics-types@0.8.4': {} + '@firebase/analytics-types@0.8.3': {} - '@firebase/analytics@0.10.22(@firebase/app@0.15.1)': + '@firebase/analytics@0.10.19(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-check-compat@0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-check': 0.12.0(@firebase/app@0.15.1) - '@firebase/app-check-types': 0.5.4 - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app-check': 0.11.0(@firebase/app@0.14.7) + '@firebase/app-check-types': 0.5.3 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/app-check-interop-types@0.3.4': {} + '@firebase/app-check-interop-types@0.3.3': {} - '@firebase/app-check-types@0.5.4': {} + '@firebase/app-check-types@0.5.3': {} - '@firebase/app-check@0.12.0(@firebase/app@0.15.1)': + '@firebase/app-check@0.11.0(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-compat@0.5.15': + '@firebase/app-compat@0.5.7': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-types@0.9.5': - dependencies: - '@firebase/logger': 0.5.1 + '@firebase/app-types@0.9.3': {} - '@firebase/app@0.15.1': + '@firebase/app@0.14.7': dependencies: - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.6.8(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/auth-compat@0.6.2(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/auth': 1.13.3(@firebase/app@0.15.1) - '@firebase/auth-types': 0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/auth': 1.12.0(@firebase/app@0.14.7) + '@firebase/auth-types': 0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) + '@firebase/component': 0.7.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/app-types' - '@react-native-async-storage/async-storage' - '@firebase/auth-interop-types@0.2.5': {} + '@firebase/auth-interop-types@0.2.4': {} - '@firebase/auth-types@0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/auth-types@0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)': dependencies: - '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/app-types': 0.9.3 + '@firebase/util': 1.13.0 - '@firebase/auth@1.13.3(@firebase/app@0.15.1)': + '@firebase/auth@1.12.0(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/component@0.7.3': + '@firebase/component@0.7.0': dependencies: - '@firebase/util': 1.15.1 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/data-connect@0.7.1(@firebase/app@0.15.1)': + '@firebase/data-connect@0.3.12(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/auth-interop-types': 0.2.4 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/database-compat@2.1.4': + '@firebase/database-compat@2.1.0': dependencies: - '@firebase/component': 0.7.3 - '@firebase/database': 1.1.3 - '@firebase/database-types': 1.0.20 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/component': 0.7.0 + '@firebase/database': 1.1.0 + '@firebase/database-types': 1.0.16 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/database-types@1.0.20': + '@firebase/database-types@1.0.16': dependencies: - '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/app-types': 0.9.3 + '@firebase/util': 1.13.0 - '@firebase/database@1.1.3': + '@firebase/database@1.1.0': dependencies: - '@firebase/app-check-interop-types': 0.3.4 - '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app-check-interop-types': 0.3.3 + '@firebase/auth-interop-types': 0.2.4 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 faye-websocket: 0.11.4 tslib: 2.8.1 - '@firebase/firestore-compat@0.4.11(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/firestore-compat@0.4.4(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/firestore': 4.16.0(@firebase/app@0.15.1) - '@firebase/firestore-types': 3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/firestore': 4.10.0(@firebase/app@0.14.7) + '@firebase/firestore-types': 3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/app-types' - '@firebase/firestore-types@3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/firestore-types@3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)': dependencies: - '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/app-types': 0.9.3 + '@firebase/util': 1.13.0 - '@firebase/firestore@4.16.0(@firebase/app@0.15.1)': + '@firebase/firestore@4.10.0(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 - '@firebase/webchannel-wrapper': 1.0.6 - '@grpc/grpc-js': 1.9.16 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 + '@firebase/webchannel-wrapper': 1.0.5 + '@grpc/grpc-js': 1.9.15 '@grpc/proto-loader': 0.7.15 - re2js: 0.4.3 tslib: 2.8.1 - '@firebase/functions-compat@0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/functions': 0.13.5(@firebase/app@0.15.1) - '@firebase/functions-types': 0.6.4 - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/functions': 0.13.1(@firebase/app@0.14.7) + '@firebase/functions-types': 0.6.3 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/functions-types@0.6.4': {} + '@firebase/functions-types@0.6.3': {} - '@firebase/functions@0.13.5(@firebase/app@0.15.1)': + '@firebase/functions@0.13.1(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/app-check-interop-types': 0.3.4 - '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 - '@firebase/messaging-interop-types': 0.2.5 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/app-check-interop-types': 0.3.3 + '@firebase/auth-interop-types': 0.2.4 + '@firebase/component': 0.7.0 + '@firebase/messaging-interop-types': 0.2.3 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/installations-compat@0.2.22(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/installations-types': 0.5.4(@firebase/app-types@0.9.5) - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations-types': 0.5.3(@firebase/app-types@0.9.3) + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/app-types' - '@firebase/installations-types@0.5.4(@firebase/app-types@0.9.5)': + '@firebase/installations-types@0.5.3(@firebase/app-types@0.9.3)': dependencies: - '@firebase/app-types': 0.9.5 + '@firebase/app-types': 0.9.3 - '@firebase/installations@0.6.22(@firebase/app@0.15.1)': + '@firebase/installations@0.6.19(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/util': 1.13.0 idb: 7.1.1 tslib: 2.8.1 - '@firebase/logger@0.5.1': + '@firebase/logger@0.5.0': dependencies: tslib: 2.8.1 - '@firebase/messaging-compat@0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/messaging': 0.13.0(@firebase/app@0.15.1) - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/messaging': 0.12.23(@firebase/app@0.14.7) + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/messaging-interop-types@0.2.5': {} + '@firebase/messaging-interop-types@0.2.3': {} - '@firebase/messaging@0.13.0(@firebase/app@0.15.1)': + '@firebase/messaging@0.12.23(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/messaging-interop-types': 0.2.5 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/messaging-interop-types': 0.2.3 + '@firebase/util': 1.13.0 idb: 7.1.1 tslib: 2.8.1 - '@firebase/performance-compat@0.2.25(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/performance': 0.7.12(@firebase/app@0.15.1) - '@firebase/performance-types': 0.2.4 - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/performance': 0.7.9(@firebase/app@0.14.7) + '@firebase/performance-types': 0.2.3 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/performance-types@0.2.4': {} + '@firebase/performance-types@0.2.3': {} - '@firebase/performance@0.7.12(@firebase/app@0.15.1)': + '@firebase/performance@0.7.9(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 web-vitals: 4.2.4 - '@firebase/remote-config-compat@0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/remote-config-compat@0.2.21(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/logger': 0.5.1 - '@firebase/remote-config': 0.9.0(@firebase/app@0.15.1) - '@firebase/remote-config-types': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/logger': 0.5.0 + '@firebase/remote-config': 0.8.0(@firebase/app@0.14.7) + '@firebase/remote-config-types': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/remote-config-types@0.5.1': {} + '@firebase/remote-config-types@0.5.0': {} - '@firebase/remote-config@0.9.0(@firebase/app@0.15.1)': + '@firebase/remote-config@0.8.0(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/logger': 0.5.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/storage-compat@0.4.3(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/storage': 0.14.3(@firebase/app@0.15.1) - '@firebase/storage-types': 0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/util': 1.15.1 + '@firebase/app-compat': 0.5.7 + '@firebase/component': 0.7.0 + '@firebase/storage': 0.14.0(@firebase/app@0.14.7) + '@firebase/storage-types': 0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) + '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: - '@firebase/app' - '@firebase/app-types' - '@firebase/storage-types@0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/storage-types@0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0)': dependencies: - '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/app-types': 0.9.3 + '@firebase/util': 1.13.0 - '@firebase/storage@0.14.3(@firebase/app@0.15.1)': + '@firebase/storage@0.14.0(@firebase/app@0.14.7)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.14.7 + '@firebase/component': 0.7.0 + '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/util@1.15.1': + '@firebase/util@1.13.0': dependencies: tslib: 2.8.1 - '@firebase/webchannel-wrapper@1.0.6': {} + '@firebase/webchannel-wrapper@1.0.5': {} '@formatjs/ecma402-abstract@2.3.6': dependencies: @@ -11342,76 +11831,74 @@ snapshots: dependencies: tslib: 2.8.1 - '@google-cloud/cloud-sql-connector@1.11.2': + '@google-cloud/cloud-sql-connector@1.9.0': dependencies: - '@googleapis/sqladmin': 37.0.0 - gaxios: 7.2.0(supports-color@10.2.2) - google-auth-library: 10.9.0(supports-color@10.2.2) + '@googleapis/sqladmin': 35.2.0 + gaxios: 7.1.3(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) p-throttle: 7.0.0 transitivePeerDependencies: - supports-color - '@google-cloud/common@6.0.1(supports-color@10.2.2)': + '@google-cloud/common@6.0.0(supports-color@10.2.2)': dependencies: '@google-cloud/projectify': 4.0.0 '@google-cloud/promisify': 4.1.0 arrify: 2.0.1 duplexify: 4.1.3 extend: 3.0.2 - google-auth-library: 10.9.0(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) html-entities: 2.6.0 - retry-request: 8.0.3(supports-color@10.2.2) - teeny-request: 10.1.3(supports-color@10.2.2) + retry-request: 8.0.2(supports-color@10.2.2) + teeny-request: 10.1.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@google-cloud/paginator@6.0.2': + '@google-cloud/paginator@6.0.0': dependencies: extend: 3.0.2 - '@google-cloud/precise-date@5.1.0': {} + '@google-cloud/precise-date@5.0.0': {} '@google-cloud/projectify@4.0.0': {} - '@google-cloud/projectify@5.1.0': {} + '@google-cloud/projectify@5.0.0': {} '@google-cloud/promisify@4.1.0': {} - '@google-cloud/promisify@5.1.0': {} + '@google-cloud/promisify@5.0.0': {} - '@google-cloud/pubsub@5.3.1': + '@google-cloud/pubsub@5.2.3': dependencies: - '@google-cloud/paginator': 6.0.2 - '@google-cloud/precise-date': 5.1.0 - '@google-cloud/projectify': 5.1.0 - '@google-cloud/promisify': 5.1.0 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.39.0 + '@google-cloud/paginator': 6.0.0 + '@google-cloud/precise-date': 5.0.0 + '@google-cloud/projectify': 5.0.0 + '@google-cloud/promisify': 5.0.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.34.0 arrify: 2.0.1 extend: 3.0.2 - google-auth-library: 10.9.0(supports-color@10.2.2) - google-gax: 5.0.7(supports-color@10.2.2) - google-logging-utils: 1.1.4 + google-auth-library: 10.5.0(supports-color@10.2.2) + google-gax: 5.0.6(supports-color@10.2.2) heap-js: 2.7.1 is-stream-ended: 0.1.4 lodash.snakecase: 4.1.1 - long: 5.3.2 p-defer: 3.0.0 transitivePeerDependencies: - supports-color '@google-cloud/spanner@8.0.0(supports-color@10.2.2)': dependencies: - '@google-cloud/common': 6.0.1(supports-color@10.2.2) - '@google-cloud/precise-date': 5.1.0 - '@google-cloud/projectify': 5.1.0 - '@google-cloud/promisify': 5.1.0 + '@google-cloud/common': 6.0.0(supports-color@10.2.2) + '@google-cloud/precise-date': 5.0.0 + '@google-cloud/projectify': 5.0.0 + '@google-cloud/promisify': 5.0.0 '@grpc/proto-loader': 0.7.15 - '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/big.js': 6.2.2 '@types/stack-trace': 0.0.33 big.js: 7.0.1 @@ -11419,437 +11906,327 @@ snapshots: duplexify: 4.1.3 events-intercept: 2.0.0 extend: 3.0.2 - google-auth-library: 10.9.0(supports-color@10.2.2) - google-gax: 5.0.7(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) + google-gax: 5.0.6(supports-color@10.2.2) grpc-gcp: 1.0.1 is: 3.3.2 lodash.snakecase: 4.1.1 merge-stream: 2.0.0 p-queue: 6.6.2 - protobufjs: 8.7.1 - retry-request: 8.0.3(supports-color@10.2.2) + protobufjs: 7.5.4 + retry-request: 8.0.2(supports-color@10.2.2) split-array-stream: 2.0.0 stack-trace: 0.0.10 stream-events: 1.0.5 - teeny-request: 10.1.3(supports-color@10.2.2) + teeny-request: 10.1.0(supports-color@10.2.2) through2: 4.0.2 transitivePeerDependencies: - supports-color - '@google/genai@2.12.0(@modelcontextprotocol/sdk@1.29.0)(bufferutil@4.1.0)(supports-color@10.2.2)(utf-8-validate@6.0.6)': + '@google/genai@1.38.0(@modelcontextprotocol/sdk@1.26.0)(bufferutil@4.1.0)(supports-color@10.2.2)(utf-8-validate@6.0.6)': dependencies: - google-auth-library: 10.9.0(supports-color@10.2.2) - p-retry: 4.6.2 - protobufjs: 8.7.1 - ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + google-auth-library: 10.5.0(supports-color@10.2.2) + protobufjs: 7.5.4 + ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - '@modelcontextprotocol/sdk': 1.29.0 + '@modelcontextprotocol/sdk': 1.26.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@googleapis/sqladmin@37.0.0': + '@googleapis/sqladmin@35.2.0': dependencies: - googleapis-common: 8.0.2 + googleapis-common: 8.0.1 transitivePeerDependencies: - supports-color - '@grpc/grpc-js@1.14.4': + '@grpc/grpc-js@1.14.3': dependencies: - '@grpc/proto-loader': 0.8.1 + '@grpc/proto-loader': 0.8.0 '@js-sdsl/ordered-map': 4.4.2 - '@grpc/grpc-js@1.9.16': + '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.15 - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@grpc/proto-loader@0.7.15': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 8.7.1 - yargs: 17.7.3 + protobufjs: 7.5.4 + yargs: 17.7.2 - '@grpc/proto-loader@0.8.1': + '@grpc/proto-loader@0.8.0': dependencies: lodash.camelcase: 4.3.0 long: 5.3.2 - protobufjs: 8.7.1 - yargs: 17.7.3 + protobufjs: 7.5.4 + yargs: 17.7.2 '@harperfast/extended-iterable@1.0.3': optional: true - '@hono/node-server@1.19.14(hono@4.12.30)': + '@hono/node-server@1.19.9(hono@4.11.9)': dependencies: - hono: 4.12.30 + hono: 4.11.9 '@inquirer/ansi@1.0.2': {} - '@inquirer/ansi@2.0.7': {} + '@inquirer/ansi@2.0.3': {} - '@inquirer/checkbox@4.3.2(@types/node@22.20.1)': + '@inquirer/checkbox@4.3.2(@types/node@22.19.11)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/type': 3.0.10(@types/node@22.19.11) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/checkbox@5.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/checkbox@5.2.1(@types/node@24.13.3)': + '@inquirer/checkbox@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/confirm@5.1.21(@types/node@22.20.1)': + '@inquirer/confirm@5.1.21(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/confirm@6.1.1(@types/node@22.20.1)': + '@inquirer/confirm@6.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/confirm@6.1.1(@types/node@24.13.3)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) - optionalDependencies: - '@types/node': 24.13.3 - - '@inquirer/core@10.3.2(@types/node@22.20.1)': + '@inquirer/core@10.3.2(@types/node@22.19.11)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/type': 3.0.10(@types/node@22.19.11) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/core@11.2.1(@types/node@22.20.1)': + '@inquirer/core@11.1.3(@types/node@24.10.9)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/ansi': 2.0.3 + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@24.10.9) cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 + fast-wrap-ansi: 0.2.0 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/core@11.2.1(@types/node@24.13.3)': + '@inquirer/editor@4.2.23(@types/node@22.19.11)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 - signal-exit: 4.1.0 + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 22.19.11 - '@inquirer/editor@4.2.23(@types/node@22.20.1)': + '@inquirer/editor@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/external-editor': 1.0.3(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/external-editor': 2.0.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/editor@5.2.2(@types/node@22.20.1)': + '@inquirer/expand@4.0.23(@types/node@22.19.11)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/external-editor': 3.0.3(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/editor@5.2.2(@types/node@24.13.3)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/external-editor': 3.0.3(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) - optionalDependencies: - '@types/node': 24.13.3 - - '@inquirer/expand@4.0.23(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/expand@5.1.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/expand@5.1.1(@types/node@24.13.3)': + '@inquirer/expand@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/external-editor@1.0.3(@types/node@22.20.1)': + '@inquirer/external-editor@1.0.3(@types/node@22.19.11)': dependencies: - chardet: 2.2.0 - iconv-lite: 0.7.3 + chardet: 2.1.1 + iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/external-editor@3.0.3(@types/node@22.20.1)': + '@inquirer/external-editor@2.0.3(@types/node@24.10.9)': dependencies: - chardet: 2.2.0 - iconv-lite: 0.7.3 + chardet: 2.1.1 + iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/external-editor@3.0.3(@types/node@24.13.3)': - dependencies: - chardet: 2.2.0 - iconv-lite: 0.7.3 - optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 '@inquirer/figures@1.0.15': {} - '@inquirer/figures@2.0.7': {} - - '@inquirer/input@4.3.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 + '@inquirer/figures@2.0.3': {} - '@inquirer/input@5.1.2(@types/node@22.20.1)': + '@inquirer/input@4.3.1(@types/node@22.19.11)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/input@5.1.2(@types/node@24.13.3)': + '@inquirer/input@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/number@3.0.23(@types/node@22.20.1)': + '@inquirer/number@3.0.23(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/number@4.1.1(@types/node@22.20.1)': + '@inquirer/number@4.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/number@4.1.1(@types/node@24.13.3)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) - optionalDependencies: - '@types/node': 24.13.3 - - '@inquirer/password@4.0.23(@types/node@22.20.1)': + '@inquirer/password@4.0.23(@types/node@22.19.11)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/password@5.1.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/password@5.1.1(@types/node@24.13.3)': + '@inquirer/password@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 - - '@inquirer/prompts@7.10.1(@types/node@22.20.1)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@22.20.1) - '@inquirer/confirm': 5.1.21(@types/node@22.20.1) - '@inquirer/editor': 4.2.23(@types/node@22.20.1) - '@inquirer/expand': 4.0.23(@types/node@22.20.1) - '@inquirer/input': 4.3.1(@types/node@22.20.1) - '@inquirer/number': 3.0.23(@types/node@22.20.1) - '@inquirer/password': 4.0.23(@types/node@22.20.1) - '@inquirer/rawlist': 4.1.11(@types/node@22.20.1) - '@inquirer/search': 3.2.2(@types/node@22.20.1) - '@inquirer/select': 4.4.2(@types/node@22.20.1) + '@types/node': 24.10.9 + + '@inquirer/prompts@7.10.1(@types/node@22.19.11)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.19.11) + '@inquirer/confirm': 5.1.21(@types/node@22.19.11) + '@inquirer/editor': 4.2.23(@types/node@22.19.11) + '@inquirer/expand': 4.0.23(@types/node@22.19.11) + '@inquirer/input': 4.3.1(@types/node@22.19.11) + '@inquirer/number': 3.0.23(@types/node@22.19.11) + '@inquirer/password': 4.0.23(@types/node@22.19.11) + '@inquirer/rawlist': 4.1.11(@types/node@22.19.11) + '@inquirer/search': 3.2.2(@types/node@22.19.11) + '@inquirer/select': 4.4.2(@types/node@22.19.11) optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/prompts@8.5.2(@types/node@22.20.1)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@22.20.1) - '@inquirer/confirm': 6.1.1(@types/node@22.20.1) - '@inquirer/editor': 5.2.2(@types/node@22.20.1) - '@inquirer/expand': 5.1.1(@types/node@22.20.1) - '@inquirer/input': 5.1.2(@types/node@22.20.1) - '@inquirer/number': 4.1.1(@types/node@22.20.1) - '@inquirer/password': 5.1.1(@types/node@22.20.1) - '@inquirer/rawlist': 5.3.1(@types/node@22.20.1) - '@inquirer/search': 4.2.1(@types/node@22.20.1) - '@inquirer/select': 5.2.1(@types/node@22.20.1) + '@types/node': 22.19.11 + + '@inquirer/prompts@8.2.0(@types/node@24.10.9)': + dependencies: + '@inquirer/checkbox': 5.0.6(@types/node@24.10.9) + '@inquirer/confirm': 6.0.6(@types/node@24.10.9) + '@inquirer/editor': 5.0.6(@types/node@24.10.9) + '@inquirer/expand': 5.0.6(@types/node@24.10.9) + '@inquirer/input': 5.0.6(@types/node@24.10.9) + '@inquirer/number': 4.0.6(@types/node@24.10.9) + '@inquirer/password': 5.0.6(@types/node@24.10.9) + '@inquirer/rawlist': 5.2.2(@types/node@24.10.9) + '@inquirer/search': 4.1.2(@types/node@24.10.9) + '@inquirer/select': 5.0.6(@types/node@24.10.9) optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/prompts@8.5.2(@types/node@24.13.3)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@24.13.3) - '@inquirer/confirm': 6.1.1(@types/node@24.13.3) - '@inquirer/editor': 5.2.2(@types/node@24.13.3) - '@inquirer/expand': 5.1.1(@types/node@24.13.3) - '@inquirer/input': 5.1.2(@types/node@24.13.3) - '@inquirer/number': 4.1.1(@types/node@24.13.3) - '@inquirer/password': 5.1.1(@types/node@24.13.3) - '@inquirer/rawlist': 5.3.1(@types/node@24.13.3) - '@inquirer/search': 4.2.1(@types/node@24.13.3) - '@inquirer/select': 5.2.1(@types/node@24.13.3) - optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/rawlist@4.1.11(@types/node@22.20.1)': + '@inquirer/rawlist@4.1.11(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/rawlist@5.3.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/rawlist@5.3.1(@types/node@24.13.3)': + '@inquirer/rawlist@5.2.2(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/search@3.2.2(@types/node@22.20.1)': + '@inquirer/search@3.2.2(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.3.2(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/type': 3.0.10(@types/node@22.19.11) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/search@4.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/search@4.2.1(@types/node@24.13.3)': + '@inquirer/search@4.1.2(@types/node@24.10.9)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 24.10.9 - '@inquirer/select@4.4.2(@types/node@22.20.1)': + '@inquirer/select@4.4.2(@types/node@22.19.11)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.20.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.20.1) + '@inquirer/type': 3.0.10(@types/node@22.19.11) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@inquirer/select@5.2.1(@types/node@22.20.1)': + '@inquirer/select@5.0.6(@types/node@24.10.9)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.3(@types/node@24.10.9) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@24.10.9) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/select@5.2.1(@types/node@24.13.3)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@24.13.3) + '@inquirer/type@3.0.10(@types/node@22.19.11)': optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 22.19.11 - '@inquirer/type@3.0.10(@types/node@22.20.1)': + '@inquirer/type@4.0.3(@types/node@24.10.9)': optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 24.10.9 - '@inquirer/type@4.0.7(@types/node@22.20.1)': - optionalDependencies: - '@types/node': 22.20.1 + '@isaacs/balanced-match@4.0.1': {} - '@inquirer/type@4.0.7(@types/node@24.13.3)': - optionalDependencies: - '@types/node': 24.13.3 + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -11858,9 +12235,9 @@ snapshots: '@isaacs/fs-minipass@4.0.1': dependencies: - minipass: 7.1.3 + minipass: 7.1.2 - '@istanbuljs/schema@0.1.6': {} + '@istanbuljs/schema@0.1.3': {} '@jasminejs/reporters@1.0.0': {} @@ -11921,59 +12298,58 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-core@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - thingies: 2.6.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - thingies: 2.6.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - glob-to-regex.js: 1.2.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.6.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) '@jsonjoy.com/json-pack': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) tslib: 2.8.1 @@ -11986,7 +12362,7 @@ snapshots: '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.6.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -11998,7 +12374,7 @@ snapshots: '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 2.6.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -12027,78 +12403,100 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@listr2/prompt-adapter-inquirer@4.2.4(@inquirer/prompts@8.5.2(@types/node@22.20.1))(@types/node@22.20.1)(listr2@10.2.2)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@22.19.11))(@types/node@22.19.11)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - listr2: 10.2.2 + '@inquirer/prompts': 7.10.1(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) + listr2: 9.0.5 transitivePeerDependencies: - '@types/node' - '@lmdb/lmdb-darwin-arm64@3.5.6': + '@lmdb/lmdb-darwin-arm64@3.5.1': optional: true - '@lmdb/lmdb-darwin-x64@3.5.6': + '@lmdb/lmdb-darwin-x64@3.5.1': optional: true - '@lmdb/lmdb-linux-arm64@3.5.6': + '@lmdb/lmdb-linux-arm64@3.5.1': optional: true - '@lmdb/lmdb-linux-arm@3.5.6': + '@lmdb/lmdb-linux-arm@3.5.1': optional: true - '@lmdb/lmdb-linux-x64@3.5.6': + '@lmdb/lmdb-linux-x64@3.5.1': optional: true - '@lmdb/lmdb-win32-arm64@3.5.6': + '@lmdb/lmdb-win32-arm64@3.5.1': optional: true - '@lmdb/lmdb-win32-x64@3.5.6': + '@lmdb/lmdb-win32-x64@3.5.1': optional: true - '@material/material-color-utilities@0.4.0': {} + '@material/material-color-utilities@0.3.0': {} - '@modelcontextprotocol/sdk@1.29.0': + '@modelcontextprotocol/sdk@1.25.3(hono@4.11.9)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.30) - ajv: 8.20.0 + '@hono/node-server': 1.19.9(hono@4.11.9) + ajv: 8.18.0 ajv-formats: 3.0.1 content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.1.0 + eventsource-parser: 3.0.6 express: 5.2.1 - express-rate-limit: 8.5.2(express@5.2.1) - hono: 4.12.30 - jose: 6.2.3 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) transitivePeerDependencies: + - hono - supports-color - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + '@modelcontextprotocol/sdk@1.26.0': + dependencies: + '@hono/node-server': 1.19.9(hono@4.11.9) + ajv: 8.18.0 + ajv-formats: 3.0.1 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.2.1(express@5.2.1) + hono: 4.11.9 + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': optional: true - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@mswjs/interceptors@0.41.9': + '@mswjs/interceptors@0.39.8': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -12179,27 +12577,29 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 optional: true - '@nginfra/angular-linking@1.0.11(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))': + '@nginfra/angular-linking@1.0.9(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))': dependencies: - '@angular/compiler-cli': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) - '@babel/core': 8.0.1 + '@angular/compiler-cli': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) + '@babel/core': 7.26.10 '@types/babel__core': 7.20.5 - '@types/node': 22.20.1 + '@types/node': 22.19.11 tinyglobby: 0.2.12 - typescript: 6.0.2 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color - '@ngtools/webpack@22.1.0-next.4(@angular/compiler-cli@22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2))(typescript@6.0.2)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16))': + '@ngtools/webpack@21.2.0-next.2(@angular/compiler-cli@21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.105.0(esbuild@0.27.2))': dependencies: - '@angular/compiler-cli': 22.1.0-next.6(@angular/compiler@22.1.0-next.6)(typescript@6.0.2) - typescript: 6.0.2 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + '@angular/compiler-cli': 21.2.0-next.3(@angular/compiler@21.2.0-next.3)(typescript@5.9.2) + typescript: 5.9.2 + webpack: 5.105.0(esbuild@0.27.2) '@noble/hashes@1.4.0': {} @@ -12215,14 +12615,77 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@octokit/auth-app@8.2.0': + '@npmcli/agent@4.0.0': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + lru-cache: 11.2.6 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@5.0.0': + dependencies: + semver: 7.7.4 + + '@npmcli/git@7.0.1': + dependencies: + '@npmcli/promise-spawn': 9.0.1 + ini: 6.0.0 + lru-cache: 11.2.6 + npm-pick-manifest: 11.0.3 + proc-log: 6.1.0 + promise-retry: 2.0.1 + semver: 7.7.4 + which: 6.0.1 + + '@npmcli/installed-package-contents@4.0.0': + dependencies: + npm-bundled: 5.0.0 + npm-normalize-package-bin: 5.0.0 + + '@npmcli/node-gyp@5.0.0': {} + + '@npmcli/package-json@7.0.4': + dependencies: + '@npmcli/git': 7.0.1 + glob: 13.0.3 + hosted-git-info: 9.0.2 + json-parse-even-better-errors: 5.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + '@npmcli/promise-spawn@3.0.0': + dependencies: + infer-owner: 1.0.4 + + '@npmcli/promise-spawn@9.0.1': + dependencies: + which: 6.0.1 + + '@npmcli/redact@4.0.0': {} + + '@npmcli/run-script@10.0.3': + dependencies: + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.4 + '@npmcli/promise-spawn': 9.0.1 + node-gyp: 12.2.0 + proc-log: 6.1.0 + which: 6.0.1 + transitivePeerDependencies: + - supports-color + + '@octokit/auth-app@8.1.2': dependencies: '@octokit/auth-oauth-app': 9.0.3 '@octokit/auth-oauth-user': 6.0.2 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 - toad-cache: 3.7.4 + toad-cache: 3.7.0 universal-github-app-jwt: 2.2.2 universal-user-agent: 7.0.3 @@ -12230,14 +12693,14 @@ snapshots: dependencies: '@octokit/auth-oauth-device': 8.0.3 '@octokit/auth-oauth-user': 6.0.2 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/auth-oauth-device@8.0.3': dependencies: '@octokit/oauth-methods': 6.0.2 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -12245,7 +12708,7 @@ snapshots: dependencies: '@octokit/auth-oauth-device': 8.0.3 '@octokit/oauth-methods': 6.0.2 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -12255,25 +12718,25 @@ snapshots: dependencies: '@octokit/auth-token': 6.0.0 '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.3': + '@octokit/endpoint@11.0.2': dependencies: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/graphql-schema@15.26.1': dependencies: - graphql: 16.14.2 - graphql-tag: 2.12.7(graphql@16.14.2) + graphql: 16.12.0 + graphql-tag: 2.12.6(graphql@16.12.0) '@octokit/graphql@9.0.3': dependencies: - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 @@ -12282,7 +12745,7 @@ snapshots: '@octokit/oauth-methods@6.0.2': dependencies: '@octokit/oauth-authorization-url': 8.0.0 - '@octokit/request': 10.0.11 + '@octokit/request': 10.0.7 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 @@ -12306,13 +12769,12 @@ snapshots: dependencies: '@octokit/types': 16.0.0 - '@octokit/request@10.0.11': + '@octokit/request@10.0.7': dependencies: - '@octokit/endpoint': 11.0.3 + '@octokit/endpoint': 11.0.2 '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 - content-type: 2.0.0 - json-with-bigint: 3.5.10 + fast-content-type-parse: 3.0.0 universal-user-agent: 7.0.3 '@octokit/rest@22.0.1': @@ -12337,258 +12799,258 @@ snapshots: '@opentelemetry/api-logs@0.57.2': dependencies: - '@opentelemetry/api': 1.9.1 + '@opentelemetry/api': 1.9.0 - '@opentelemetry/api@1.9.1': {} + '@opentelemetry/api@1.9.0': {} - '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 + '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks@2.9.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 + '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 + '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/instrumentation-amqplib@0.46.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-amqplib@0.46.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.43.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-connect@0.43.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.16.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-dataloader@0.16.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.47.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-express@0.47.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.19.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-fs@0.19.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.43.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-generic-pool@0.43.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.47.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-graphql@0.47.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.45.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-hapi@0.45.2(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.57.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-http@0.57.2(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 forwarded-parse: 2.1.2 - semver: 7.8.5 + semver: 7.7.4 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.47.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-ioredis@0.47.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-kafkajs@0.7.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-knex@0.44.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-knex@0.44.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.47.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-koa@0.47.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.44.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-lru-memoizer@0.44.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.52.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-mongodb@0.52.0(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.46.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-mongoose@0.46.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.45.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-mysql2@0.45.2(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.45.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-mysql@0.45.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/mysql': 2.15.26 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.51.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-pg@0.51.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 - '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) '@types/pg': 8.6.1 '@types/pg-pool': 2.0.6 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis-4@0.46.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-redis-4@0.46.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/semantic-conventions': 1.39.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.18.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-tedious@0.18.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.10.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-undici@0.10.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 + '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 import-in-the-middle: 1.15.0 require-in-the-middle: 7.5.2 - semver: 7.8.5 + semver: 7.7.4 shimmer: 1.2.1 transitivePeerDependencies: - supports-color '@opentelemetry/redis-common@0.36.2': {} - '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 '@opentelemetry/semantic-conventions@1.28.0': {} - '@opentelemetry/semantic-conventions@1.39.0': {} + '@opentelemetry/semantic-conventions@1.34.0': {} - '@opentelemetry/semantic-conventions@1.43.0': {} + '@opentelemetry/semantic-conventions@1.39.0': {} - '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@oxc-project/types@0.139.0': {} + '@oxc-project/types@0.112.0': {} '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -12634,7 +13096,7 @@ snapshots: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.1.1 - picomatch: 4.0.5 + picomatch: 4.0.3 optionalDependencies: '@parcel/watcher-android-arm64': 2.5.6 '@parcel/watcher-darwin-arm64': 2.5.6 @@ -12651,130 +13113,126 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true - '@paulirish/trace_engine@0.0.65': + '@paulirish/trace_engine@0.0.61': dependencies: legacy-javascript: 0.0.1 - third-party-web: 0.29.2 - - '@peculiar/asn1-cms@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 + third-party-web: 0.29.0 - '@peculiar/asn1-csr@2.8.0': + '@peculiar/asn1-cms@2.6.1': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + '@peculiar/asn1-x509-attr': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.8.0': + '@peculiar/asn1-csr@2.6.1': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-pfx@2.8.0': + '@peculiar/asn1-ecc@2.6.1': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-pkcs8@2.8.0': + '@peculiar/asn1-pfx@2.6.1': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-cms': 2.6.1 + '@peculiar/asn1-pkcs8': 2.6.1 + '@peculiar/asn1-rsa': 2.6.1 + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-pkcs9@2.8.0': + '@peculiar/asn1-pkcs8@2.6.1': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pfx': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.8.0': + '@peculiar/asn1-pkcs9@2.6.1': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 + '@peculiar/asn1-cms': 2.6.1 + '@peculiar/asn1-pfx': 2.6.1 + '@peculiar/asn1-pkcs8': 2.6.1 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + '@peculiar/asn1-x509-attr': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-schema@2.8.0': + '@peculiar/asn1-rsa@2.6.1': dependencies: - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/asn1-x509-attr@2.8.0': + '@peculiar/asn1-schema@2.6.0': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 + asn1js: 3.0.7 + pvtsutils: 1.3.6 tslib: 2.8.1 - '@peculiar/asn1-x509@2.8.0': + '@peculiar/asn1-x509-attr@2.6.1': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 + asn1js: 3.0.7 tslib: 2.8.1 - '@peculiar/utils@2.0.3': + '@peculiar/asn1-x509@2.6.1': dependencies: + '@peculiar/asn1-schema': 2.6.0 + asn1js: 3.0.7 + pvtsutils: 1.3.6 tslib: 2.8.1 '@peculiar/x509@1.14.3': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-csr': 2.8.0 - '@peculiar/asn1-ecc': 2.8.0 - '@peculiar/asn1-pkcs9': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-cms': 2.6.1 + '@peculiar/asn1-csr': 2.6.1 + '@peculiar/asn1-ecc': 2.6.1 + '@peculiar/asn1-pkcs9': 2.6.1 + '@peculiar/asn1-rsa': 2.6.1 + '@peculiar/asn1-schema': 2.6.0 + '@peculiar/asn1-x509': 2.6.1 pvtsutils: 1.3.6 reflect-metadata: 0.2.2 tslib: 2.8.1 tsyringe: 4.10.0 - '@phenomnomnominal/tsquery@4.2.0(typescript@6.0.2)': + '@phenomnomnominal/tsquery@4.2.0(typescript@5.9.2)': dependencies: esquery: 1.7.0 - typescript: 6.0.2 + typescript: 5.9.2 '@pkgjs/parseargs@0.11.0': optional: true '@pnpm/config.env-replace@1.1.0': {} - '@pnpm/crypto.hash@1000.2.2': + '@pnpm/crypto.hash@1000.2.1': dependencies: '@pnpm/crypto.polyfill': 1000.1.0 - '@pnpm/graceful-fs': 1000.1.0 + '@pnpm/graceful-fs': 1000.0.1 ssri: 10.0.5 '@pnpm/crypto.polyfill@1000.1.0': {} - '@pnpm/dependency-path@1001.1.10': + '@pnpm/dependency-path@1001.1.9': dependencies: - '@pnpm/crypto.hash': 1000.2.2 + '@pnpm/crypto.hash': 1000.2.1 '@pnpm/types': 1001.3.0 - semver: 7.8.5 + semver: 7.7.4 - '@pnpm/graceful-fs@1000.1.0': + '@pnpm/graceful-fs@1000.0.1': dependencies: graceful-fs: 4.2.11 @@ -12782,7 +13240,7 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@3.0.3': + '@pnpm/npm-conf@3.0.2': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 @@ -12790,252 +13248,310 @@ snapshots: '@pnpm/types@1001.3.0': {} - '@prisma/instrumentation@6.11.1(@opentelemetry/api@1.9.1)': + '@prisma/instrumentation@6.11.1(@opentelemetry/api@1.9.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@puppeteer/browsers@3.0.6': + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': dependencies: - modern-tar: 0.7.6 - yargs: 18.0.0 + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 - '@rolldown/binding-android-arm64@1.1.5': - optional: true + '@protobufjs/float@1.0.2': {} - '@rolldown/binding-darwin-arm64@1.1.5': - optional: true + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@puppeteer/browsers@2.12.1': + dependencies: + debug: 4.4.3(supports-color@10.2.2) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.4 + tar-fs: 3.1.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color - '@rolldown/binding-darwin-x64@1.1.5': + '@rolldown/binding-android-arm64@1.0.0-rc.3': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@rolldown/binding-darwin-x64@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': optional: true - '@rolldown/binding-wasm32-wasi@1.1.5': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': optional: true - '@rolldown/pluginutils@1.0.1': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} - '@rollup/plugin-commonjs@29.0.3(rollup@4.62.2)': + '@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.57.1) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.5) + fdir: 6.5.0(picomatch@4.0.3) is-reference: 1.2.1 magic-string: 0.30.21 - picomatch: 4.0.5 + picomatch: 4.0.3 optionalDependencies: - rollup: 4.62.2 + rollup: 4.57.1 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.2)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.57.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@rollup/pluginutils': 5.3.0(rollup@4.57.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.12 + resolve: 1.22.11 optionalDependencies: - rollup: 4.62.2 + rollup: 4.57.1 - '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + '@rollup/pluginutils@5.2.0(rollup@4.57.1)': dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.5 + picomatch: 4.0.3 optionalDependencies: - rollup: 4.62.2 + rollup: 4.57.1 - '@rollup/rollup-android-arm-eabi@4.62.2': + '@rollup/pluginutils@5.3.0(rollup@4.57.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.1 + + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true - '@rollup/rollup-android-arm64@4.62.2': + '@rollup/rollup-android-arm64@4.57.1': optional: true - '@rollup/rollup-darwin-arm64@4.62.2': + '@rollup/rollup-darwin-arm64@4.57.1': optional: true - '@rollup/rollup-darwin-x64@4.62.2': + '@rollup/rollup-darwin-x64@4.57.1': optional: true - '@rollup/rollup-freebsd-arm64@4.62.2': + '@rollup/rollup-freebsd-arm64@4.57.1': optional: true - '@rollup/rollup-freebsd-x64@4.62.2': + '@rollup/rollup-freebsd-x64@4.57.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.2': + '@rollup/rollup-linux-arm-musleabihf@4.57.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.2': + '@rollup/rollup-linux-arm64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.2': + '@rollup/rollup-linux-arm64-musl@4.57.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.2': + '@rollup/rollup-linux-loong64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.2': + '@rollup/rollup-linux-loong64-musl@4.57.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.2': + '@rollup/rollup-linux-ppc64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.2': + '@rollup/rollup-linux-ppc64-musl@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.2': + '@rollup/rollup-linux-riscv64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.2': + '@rollup/rollup-linux-riscv64-musl@4.57.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.2': + '@rollup/rollup-linux-s390x-gnu@4.57.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.2': + '@rollup/rollup-linux-x64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-x64-musl@4.62.2': + '@rollup/rollup-linux-x64-musl@4.57.1': optional: true - '@rollup/rollup-openbsd-x64@4.62.2': + '@rollup/rollup-openbsd-x64@4.57.1': optional: true - '@rollup/rollup-openharmony-arm64@4.62.2': + '@rollup/rollup-openharmony-arm64@4.57.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.2': + '@rollup/rollup-win32-arm64-msvc@4.57.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.2': + '@rollup/rollup-win32-ia32-msvc@4.57.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.2': + '@rollup/rollup-win32-x64-gnu@4.57.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.2': + '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true - '@schematics/angular@22.1.0-next.4(chokidar@5.0.0)': + '@schematics/angular@21.2.0-next.2(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 22.1.0-next.4(chokidar@5.0.0) - '@angular-devkit/schematics': 22.1.0-next.4(chokidar@5.0.0) + '@angular-devkit/core': 21.2.0-next.2(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.0-next.2(chokidar@5.0.0) jsonc-parser: 3.3.1 - typescript: 6.0.2 transitivePeerDependencies: - chokidar '@sentry/core@9.47.1': {} - '@sentry/node-core@9.47.1(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)': + '@sentry/node-core@9.47.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 9.47.1 - '@sentry/opentelemetry': 9.47.1(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) + '@sentry/opentelemetry': 9.47.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) import-in-the-middle: 1.15.0 '@sentry/node@9.47.1': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-amqplib': 0.46.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-connect': 0.43.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-dataloader': 0.16.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-express': 0.47.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-fs': 0.19.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-generic-pool': 0.43.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-graphql': 0.47.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-hapi': 0.45.2(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-http': 0.57.2(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-ioredis': 0.47.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-kafkajs': 0.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-knex': 0.44.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-koa': 0.47.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-lru-memoizer': 0.44.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-mongodb': 0.52.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-mongoose': 0.46.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-mysql': 0.45.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-mysql2': 0.45.2(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-pg': 0.51.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-redis-4': 0.46.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-tedious': 0.18.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation-undici': 0.10.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 - '@prisma/instrumentation': 6.11.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.46.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.43.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.16.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.47.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.19.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.43.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.47.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.45.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.57.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.47.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.44.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.47.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.44.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.46.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.45.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.45.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.51.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis-4': 0.46.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.18.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.10.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@prisma/instrumentation': 6.11.1(@opentelemetry/api@1.9.0) '@sentry/core': 9.47.1 - '@sentry/node-core': 9.47.1(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) - '@sentry/opentelemetry': 9.47.1(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) + '@sentry/node-core': 9.47.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + '@sentry/opentelemetry': 9.47.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) import-in-the-middle: 1.15.0 - minimatch: 9.0.9 + minimatch: 9.0.5 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@9.47.1(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0)': + '@sentry/opentelemetry@9.47.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.43.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 9.47.1 - '@simple-libs/child-process-utils@2.0.0': + '@sigstore/bundle@4.0.0': + dependencies: + '@sigstore/protobuf-specs': 0.5.0 + + '@sigstore/core@3.1.0': {} + + '@sigstore/protobuf-specs@0.5.0': {} + + '@sigstore/sign@4.1.0': + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.1.0 + '@sigstore/protobuf-specs': 0.5.0 + make-fetch-happen: 15.0.3 + proc-log: 6.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@sigstore/tuf@4.0.1': dependencies: - '@simple-libs/stream-utils': 2.0.0 + '@sigstore/protobuf-specs': 0.5.0 + tuf-js: 4.1.0 + transitivePeerDependencies: + - supports-color - '@simple-libs/stream-utils@2.0.0': {} + '@sigstore/verify@3.1.0': + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.1.0 + '@sigstore/protobuf-specs': 0.5.0 '@sindresorhus/is@4.6.0': {} @@ -13046,10 +13562,12 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@stackblitz/sdk@1.11.1': {} + '@stackblitz/sdk@1.11.0': {} '@standard-schema/spec@1.1.0': {} + '@tootallnate/once@2.0.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} '@tsconfig/node10@1.0.12': {} @@ -13060,82 +13578,99 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@tybys/wasm-util@0.10.3': + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@4.1.0': + dependencies: + '@tufjs/canonical-json': 2.0.0 + minimatch: 10.2.0 + + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.0 '@types/big.js@6.2.2': {} '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/bonjour@3.5.13': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/cli-progress@3.11.6': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 4.19.9 - '@types/node': 22.20.1 + '@types/express-serve-static-core': 4.19.8 + '@types/node': 22.19.11 '@types/connect@3.4.38': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/cors@2.8.19': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/duplexify@3.6.5': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/ejs@3.1.5': {} - '@types/estree@1.0.9': {} + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} '@types/events@3.0.3': {} - '@types/express-serve-static-core@4.19.9': + '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 22.20.1 - '@types/qs': 6.15.1 + '@types/node': 22.19.11 + '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.9 - '@types/qs': 6.15.1 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.14.0 '@types/serve-static': 1.15.10 '@types/folder-hash@4.0.4': {} @@ -13143,31 +13678,29 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@types/gensync@1.0.5': {} + '@types/git-raw-commits@5.0.1': + dependencies: + '@types/node': 22.19.11 - '@types/google.maps@3.65.2': {} + '@types/google.maps@3.58.1': {} '@types/http-errors@2.0.5': {} '@types/http-proxy@1.17.17': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/jasmine@6.0.0': {} - '@types/jsesc@2.5.1': {} - '@types/json-schema@7.0.15': {} '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.20.1 - - '@types/less@3.0.8': {} + '@types/node': 22.19.11 - '@types/luxon@3.7.2': {} + '@types/luxon@3.7.1': {} '@types/mime@1.3.5': {} @@ -13175,15 +13708,15 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - '@types/node@22.20.1': + '@types/node@22.19.11': dependencies: undici-types: 6.21.0 - '@types/node@24.13.3': + '@types/node@24.10.9': dependencies: - undici-types: 7.18.2 + undici-types: 7.16.0 '@types/normalize-package-data@2.4.4': {} @@ -13195,25 +13728,23 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 22.20.1 - pg-protocol: 1.15.0 + '@types/node': 22.19.11 + pg-protocol: 1.11.0 pg-types: 2.2.0 '@types/pumpify@1.4.5': dependencies: '@types/duplexify': 3.6.5 - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/q@0.0.32': {} - '@types/qs@6.15.1': {} + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} '@types/resolve@1.20.2': {} - '@types/retry@0.12.0': {} - '@types/retry@0.12.2': {} '@types/selenium-webdriver@3.0.26': {} @@ -13223,11 +13754,11 @@ snapshots: '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/send@1.2.1': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/serve-index@1.9.4': dependencies: @@ -13236,25 +13767,25 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/send': 0.17.6 '@types/shelljs@0.10.0': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 fast-glob: 3.3.3 '@types/shimmer@1.2.0': {} '@types/sockjs@0.3.36': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/stack-trace@0.0.33': {} '@types/tedious@4.0.14': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/triple-beam@1.3.5': {} @@ -13268,7 +13799,7 @@ snapshots: '@types/vfile@3.0.2': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/unist': 2.0.11 '@types/vfile-message': 2.0.0 @@ -13276,7 +13807,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 '@types/yargs-parser@21.0.3': {} @@ -13286,11 +13817,16 @@ snapshots: '@types/yarnpkg__lockfile@1.1.9': {} - '@types/youtube@0.3.0': {} + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 22.19.11 + optional: true + + '@types/youtube@0.1.2': {} - '@vitejs/plugin-basic-ssl@2.3.0(vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.7)(sass@1.101.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))': + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - vite: 8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.7)(sass@1.101.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) '@webassemblyjs/ast@1.14.1': dependencies: @@ -13368,7 +13904,7 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@xmldom/xmldom@0.8.13': {} + '@xmldom/xmldom@0.8.11': {} '@xtuc/ieee754@1.2.0': {} @@ -13380,8 +13916,7 @@ snapshots: abbrev@1.0.9: {} - abbrev@5.0.0: - optional: true + abbrev@4.0.0: {} abort-controller@3.0.0: dependencies: @@ -13397,38 +13932,38 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.17.0): + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: - acorn: 8.17.0 + acorn: 8.15.0 - acorn-import-phases@1.0.4(acorn@8.17.0): + acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: - acorn: 8.17.0 + acorn: 8.15.0 acorn-jsx@5.3.2(acorn@7.4.1): dependencies: acorn: 7.4.1 - acorn-walk@8.3.5: + acorn-walk@8.3.4: dependencies: - acorn: 8.17.0 + acorn: 8.15.0 acorn@7.4.1: {} - acorn@8.17.0: {} + acorn@8.15.0: {} adjust-sourcemap-loader@4.0.0: dependencies: loader-utils: 2.0.4 regex-parser: 2.3.1 - adm-zip@0.5.18: {} + adm-zip@0.5.16: {} agent-base@4.3.0: dependencies: es6-promisify: 5.0.0 - agent-base@6.0.2: + agent-base@6.0.2(supports-color@10.2.2): dependencies: debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: @@ -13436,51 +13971,56 @@ snapshots: agent-base@7.1.4: {} - agent-base@9.0.0: {} - - ajv-formats@2.1.1(ajv@8.20.0): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.20.0 + ajv: 8.18.0 ajv-formats@3.0.1: dependencies: - ajv: 8.20.0 + ajv: 8.18.0 - ajv-keywords@5.1.0(ajv@8.20.0): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.20.0 + ajv: 8.18.0 fast-deep-equal: 3.1.3 - ajv@6.15.0: + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.20.0: + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch@5.55.2: - dependencies: - '@algolia/abtesting': 1.21.2 - '@algolia/client-abtesting': 5.55.2 - '@algolia/client-analytics': 5.55.2 - '@algolia/client-common': 5.55.2 - '@algolia/client-insights': 5.55.2 - '@algolia/client-personalization': 5.55.2 - '@algolia/client-query-suggestions': 5.55.2 - '@algolia/client-search': 5.55.2 - '@algolia/ingestion': 1.55.2 - '@algolia/monitoring': 1.55.2 - '@algolia/recommend': 5.55.2 - '@algolia/requester-browser-xhr': 5.55.2 - '@algolia/requester-fetch': 5.55.2 - '@algolia/requester-node-http': 5.55.2 + algoliasearch@5.47.0: + dependencies: + '@algolia/abtesting': 1.13.0 + '@algolia/client-abtesting': 5.47.0 + '@algolia/client-analytics': 5.47.0 + '@algolia/client-common': 5.47.0 + '@algolia/client-insights': 5.47.0 + '@algolia/client-personalization': 5.47.0 + '@algolia/client-query-suggestions': 5.47.0 + '@algolia/client-search': 5.47.0 + '@algolia/ingestion': 1.47.0 + '@algolia/monitoring': 1.47.0 + '@algolia/recommend': 5.47.0 + '@algolia/requester-browser-xhr': 5.47.0 + '@algolia/requester-fetch': 5.47.0 + '@algolia/requester-node-http': 5.47.0 amdefine@1.0.1: optional: true @@ -13520,7 +14060,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.2 + picomatch: 2.3.1 archiver-utils@5.0.2: dependencies: @@ -13528,7 +14068,7 @@ snapshots: graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 - lodash: 4.18.1 + lodash: 4.17.23 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -13539,11 +14079,10 @@ snapshots: buffer-crc32: 1.0.0 readable-stream: 4.7.0 readdir-glob: 1.1.3 - tar-stream: 3.2.0 + tar-stream: 3.1.7 zip-stream: 6.0.1 transitivePeerDependencies: - bare-abort-controller - - bare-buffer - react-native-b4a arg@4.1.3: {} @@ -13554,8 +14093,6 @@ snapshots: argparse@2.0.1: {} - argue-cli@3.1.0: {} - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -13578,9 +14115,9 @@ snapshots: arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -13597,7 +14134,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - asn1js@3.0.10: + asn1js@3.0.7: dependencies: pvtsutils: 1.3.6 pvutils: 1.1.5 @@ -13619,7 +14156,7 @@ snapshots: async@2.6.4: dependencies: - lodash: 4.18.1 + lodash: 4.17.23 async@3.2.6: {} @@ -13630,22 +14167,13 @@ snapshots: stubborn-fs: 2.0.0 when-exit: 2.1.5 - autoprefixer@10.5.2(postcss@8.5.16): - dependencies: - browserslist: 4.28.6 - caniuse-lite: 1.0.30001805 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.16 - postcss-value-parser: 4.2.0 - - autoprefixer@10.5.2(postcss@8.5.19): + autoprefixer@10.4.24(postcss@8.5.6): dependencies: - browserslist: 4.28.6 - caniuse-lite: 1.0.30001805 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001770 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.19 + postcss: 8.5.6 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -13656,22 +14184,47 @@ snapshots: aws4@1.13.2: {} - axe-core@4.12.1: {} + axe-core@4.11.1: {} - b4a@1.8.1: {} + b4a@1.7.4: {} - babel-loader@10.1.1(@babel/core@8.0.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.105.0(esbuild@0.27.2)): dependencies: - '@babel/core': 8.0.1 + '@babel/core': 7.29.0 find-up: 5.0.0 - optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) + + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color - babel-plugin-polyfill-corejs3@1.0.0(@babel/core@8.0.1): + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0): dependencies: - '@babel/core': 8.0.1 - '@babel/helper-define-polyfill-provider': 1.0.0(@babel/core@8.0.1) - core-js-compat: 3.49.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color bail@1.0.5: {} @@ -13679,42 +14232,52 @@ snapshots: balanced-match@2.0.0: {} - balanced-match@4.0.4: {} + balanced-match@4.0.2: + dependencies: + jackspeak: 4.2.3 - bare-events@2.9.1: {} + bare-events@2.8.2: {} - bare-fs@4.7.4: + bare-fs@4.5.4: dependencies: - bare-events: 2.9.1 - bare-path: 3.1.1 - bare-stream: 2.13.3(bare-events@2.9.1) - bare-url: 2.4.5 + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.8.2) + bare-url: 2.3.2 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller - react-native-b4a + optional: true - bare-path@3.1.1: {} + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true - bare-stream@2.13.3(bare-events@2.9.1): + bare-stream@2.7.0(bare-events@2.8.2): dependencies: - b4a: 1.8.1 - streamx: 2.28.0 - teex: 1.0.1 + streamx: 2.23.0 optionalDependencies: - bare-events: 2.9.1 + bare-events: 2.8.2 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a + optional: true - bare-url@2.4.5: + bare-url@2.3.2: dependencies: - bare-path: 3.1.1 + bare-path: 3.0.0 + optional: true base64-js@1.5.1: {} base64id@2.0.0: {} - baseline-browser-mapping@2.10.43: {} + baseline-browser-mapping@2.9.19: {} basic-auth-connect@1.1.0: dependencies: @@ -13724,7 +14287,7 @@ snapshots: dependencies: safe-buffer: 5.1.2 - basic-ftp@5.3.1: {} + basic-ftp@5.1.0: {} batch@0.6.1: {} @@ -13732,7 +14295,7 @@ snapshots: dependencies: tweetnacl: 0.14.5 - beasties@0.4.2: + beasties@0.4.1: dependencies: css-select: 6.0.0 css-what: 7.0.0 @@ -13740,9 +14303,9 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.1.0 picocolors: 1.1.1 - postcss: 8.5.19 + postcss: 8.5.6 postcss-media-query-parser: 0.2.3 - postcss-safe-parser: 7.0.1(postcss@8.5.19) + postcss-safe-parser: 7.0.1(postcss@8.5.6) before-after-hook@4.0.0: {} @@ -13770,7 +14333,7 @@ snapshots: dependencies: minimist: 1.2.8 - body-parser@1.20.6: + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -13780,28 +14343,28 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.14.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - body-parser@2.3.0: + body-parser@2.2.2: dependencies: bytes: 3.1.2 - content-type: 2.0.0 + content-type: 1.0.5 debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 - iconv-lite: 0.7.3 + iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.15.0 raw-body: 3.0.2 - type-is: 2.1.0 + type-is: 2.0.1 transitivePeerDependencies: - supports-color - bonjour-service@1.4.3: + bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 multicast-dns: 7.2.5 @@ -13819,37 +14382,37 @@ snapshots: widest-line: 3.1.0 wrap-ansi: 7.0.0 - brace-expansion@1.1.16: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.2: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.7: + brace-expansion@5.0.2: dependencies: - balanced-match: 4.0.4 + balanced-match: 4.0.2 braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.28.6: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.10.43 - caniuse-lite: 1.0.30001805 - electron-to-chromium: 1.5.389 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.6) + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) - browserstack-local@1.5.13: + browserstack-local@1.5.10: dependencies: - agent-base: 6.0.2 - https-proxy-agent: 5.0.1 + agent-base: 6.0.2(supports-color@10.2.2) + https-proxy-agent: 5.0.1(supports-color@10.2.2) is-running: 2.1.0 - tree-kill: 1.2.2 + ps-tree: 1.2.0 transitivePeerDependencies: - supports-color @@ -13865,6 +14428,8 @@ snapshots: transitivePeerDependencies: - supports-color + buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} buffer-equal-constant-time@1.0.1: {} @@ -13897,12 +14462,26 @@ snapshots: bytestreamjs@2.0.1: {} + cacache@20.0.3: + dependencies: + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.3 + lru-cache: 11.2.6 + minipass: 7.1.2 + minipass-collect: 2.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 7.0.4 + ssri: 13.0.1 + unique-filename: 5.0.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.9: + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -13933,7 +14512,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001805: {} + caniuse-lite@1.0.30001770: {} canonical-path@0.0.2: {} @@ -13943,7 +14522,7 @@ snapshots: catharsis@0.8.11: dependencies: - lodash: 4.18.1 + lodash: 4.17.23 ccount@1.1.0: {} @@ -13995,7 +14574,7 @@ snapshots: character-entities-legacy@1.1.4: {} - chardet@2.2.0: {} + chardet@2.1.1: {} checkpoint-stream@0.1.2: dependencies: @@ -14017,6 +14596,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -14025,7 +14608,7 @@ snapshots: chrome-launcher@1.2.1: dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.2 @@ -14034,9 +14617,9 @@ snapshots: chrome-trace-event@1.0.4: {} - chromium-bidi@16.0.1(devtools-protocol@0.0.1638949): + chromium-bidi@14.0.0(devtools-protocol@0.0.1566079): dependencies: - devtools-protocol: 0.0.1638949 + devtools-protocol: 0.0.1566079 mitt: 3.0.1 zod: 3.25.76 @@ -14065,7 +14648,7 @@ snapshots: mz: 2.7.0 parse5: 5.1.1 parse5-htmlparser2-tree-adapter: 6.0.1 - yargs: 16.2.2 + yargs: 16.2.0 cli-progress@3.12.0: dependencies: @@ -14081,10 +14664,10 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-truncate@5.2.0: + cli-truncate@5.1.1: dependencies: - slice-ansi: 8.0.0 - string-width: 8.2.2 + slice-ansi: 7.1.2 + string-width: 8.1.1 cli-width@4.1.0: {} @@ -14109,7 +14692,7 @@ snapshots: cliui@9.0.1: dependencies: string-width: 7.2.0 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 wrap-ansi: 9.0.2 clone-deep@4.0.1: @@ -14165,6 +14748,8 @@ snapshots: commander@10.0.1: {} + commander@11.1.0: {} + commander@2.20.3: {} commander@5.1.0: {} @@ -14251,18 +14836,15 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.1.0: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} - content-type@2.0.0: {} + conventional-commits-filter@5.0.0: {} - conventional-commits-filter@6.0.1: {} - - conventional-commits-parser@7.1.0: + conventional-commits-parser@6.2.1: dependencies: - '@simple-libs/stream-utils': 2.0.0 - argue-cli: 3.1.0 + meow: 13.2.0 convert-source-map@1.9.0: {} @@ -14274,22 +14856,22 @@ snapshots: cookie@0.7.2: {} - copy-anything@3.0.5: + copy-anything@2.0.6: dependencies: - is-what: 4.1.16 + is-what: 3.14.1 - copy-webpack-plugin@14.0.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + copy-webpack-plugin@13.0.1(webpack@5.105.0(esbuild@0.27.2)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 - serialize-javascript: 7.0.7 - tinyglobby: 0.2.17 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + serialize-javascript: 6.0.2 + tinyglobby: 0.2.15 + webpack: 5.105.0(esbuild@0.27.2) - core-js-compat@3.49.0: + core-js-compat@3.48.0: dependencies: - browserslist: 4.28.6 + browserslist: 4.28.1 core-util-is@1.0.2: {} @@ -14306,16 +14888,16 @@ snapshots: import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 - yaml: 1.10.3 + yaml: 1.10.2 - cosmiconfig@9.0.2(typescript@6.0.2): + cosmiconfig@9.0.0(typescript@5.9.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: - typescript: 6.0.2 + typescript: 5.9.2 crc-32@1.2.2: {} @@ -14346,22 +14928,22 @@ snapshots: crypto-random-string@2.0.0: {} - csp_evaluator@1.1.8: {} + csp_evaluator@1.1.5: {} css-functions-list@3.3.3: {} - css-loader@7.1.4(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + css-loader@7.1.3(webpack@5.105.0(esbuild@0.27.2)): dependencies: - icss-utils: 5.1.0(postcss@8.5.19) - postcss: 8.5.19 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.19) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.19) - postcss-modules-scope: 3.2.1(postcss@8.5.19) - postcss-modules-values: 4.0.0(postcss@8.5.19) + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 - semver: 7.8.5 + semver: 7.7.4 optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) css-select@6.0.0: dependencies: @@ -14407,7 +14989,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - date-fns@4.4.0: {} + date-fns@4.1.0: {} date-format@4.0.14: {} @@ -14446,6 +15028,11 @@ snapshots: decimal.js@10.6.0: {} + deep-equal-in-any-order@2.1.0: + dependencies: + lodash.mapvalues: 4.6.0 + sort-any: 2.0.0 + deep-extend@0.6.0: {} deep-freeze@0.0.1: {} @@ -14509,13 +15096,14 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.1.2: {} + detect-libc@2.1.2: + optional: true detect-node@2.1.0: {} - devtools-protocol@0.0.1625959: {} + devtools-protocol@0.0.1527314: {} - devtools-protocol@0.0.1638949: {} + devtools-protocol@0.0.1566079: {} dgeni-packages@0.30.0(dgeni@0.4.14): dependencies: @@ -14530,14 +15118,14 @@ snapshots: htmlencode: 0.0.4 htmlparser2: 3.10.1 lodash.difference: 4.5.0 - lodash.template: 4.18.1 + lodash.template: 4.5.0 marked: 0.7.0 - minimatch: 3.1.5 + minimatch: 3.1.2 nunjucks: 3.2.4 rehype: 8.0.0 semver: 5.7.2 spdx-license-list: 2.1.0 - typescript: 6.0.2 + typescript: 5.9.2 urlencode: 1.1.0 transitivePeerDependencies: - chokidar @@ -14552,7 +15140,7 @@ snapshots: objectdiff: 1.1.0 validate.js: 0.12.0 winston: 2.4.7 - yargs: 16.2.2 + yargs: 16.2.0 di@0.0.1: {} @@ -14627,6 +15215,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer@0.1.2: {} + duplexify@3.7.1: dependencies: end-of-stream: 1.4.5 @@ -14654,9 +15244,11 @@ snapshots: ee-first@1.1.1: {} - ejs@6.0.1: {} + ejs@4.0.1: + dependencies: + jake: 10.9.4 - electron-to-chromium@1.5.389: {} + electron-to-chromium@1.5.286: {} emoji-regex@10.6.0: {} @@ -14668,8 +15260,6 @@ snapshots: emojis-list@3.0.0: {} - empathic@2.0.1: {} - enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -14686,27 +15276,26 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.9(bufferutil@4.1.0): + engine.io@6.6.5(bufferutil@4.1.0): dependencies: '@types/cors': 2.8.19 - '@types/node': 22.20.1 - '@types/ws': 8.18.1 + '@types/node': 22.19.11 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.6 debug: 4.4.3(supports-color@10.2.2) engine.io-parser: 5.2.3 - ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.18.3(bufferutil@4.1.0) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - enhanced-resolve@5.24.2: + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.3 + tapable: 2.3.0 enquirer@2.4.1: dependencies: @@ -14726,14 +15315,16 @@ snapshots: entities@4.5.0: {} - entities@7.0.1: {} + entities@6.0.1: {} - entities@8.0.0: {} + entities@7.0.1: {} env-paths@2.2.1: {} environment@1.1.0: {} + err-code@2.0.3: {} + errno@0.1.8: dependencies: prr: 1.0.1 @@ -14743,29 +15334,22 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract-get@1.0.0: - dependencies: - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - is-callable: 1.2.7 - object-inspect: 1.13.4 - - es-abstract@1.24.2: + es-abstract@1.24.1: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.4 - function.prototype.name: 1.2.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -14774,7 +15358,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.4 + hasown: 2.0.2 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -14792,28 +15376,28 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 + safe-array-concat: 1.1.3 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.11 - string.prototype.trimend: 1.0.10 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.8 + typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.22 + which-typed-array: 1.1.20 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-module-lexer@2.3.1: {} + es-module-lexer@2.0.0: {} - es-object-atoms@1.1.2: + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -14822,13 +15406,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 - es-to-primitive@1.3.4: + es-to-primitive@1.3.0: dependencies: - es-abstract-get: 1.0.0 - es-define-property: 1.0.1 - es-errors: 1.3.0 is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 @@ -14839,36 +15420,65 @@ snapshots: dependencies: es6-promise: 4.2.8 - esbuild-wasm@0.28.1: {} + esbuild-wasm@0.27.2: {} - esbuild@0.28.1: + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + esbuild@0.27.3: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 escalade@3.2.0: {} @@ -14934,6 +15544,16 @@ snapshots: etag@1.8.1: {} + event-stream@3.3.4: + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -14946,17 +15566,17 @@ snapshots: events-universal@1.0.1: dependencies: - bare-events: 2.9.1 + bare-events: 2.8.2 transitivePeerDependencies: - bare-abort-controller events@3.3.0: {} - eventsource-parser@3.1.0: {} + eventsource-parser@3.0.6: {} eventsource@3.0.7: dependencies: - eventsource-parser: 3.1.0 + eventsource-parser: 3.0.6 execa@5.1.1: dependencies: @@ -14979,39 +15599,42 @@ snapshots: exegesis@4.3.0: dependencies: '@apidevtools/json-schema-ref-parser': 9.1.2 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - body-parser: 1.20.6 + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + body-parser: 1.20.4 content-type: 1.0.5 deep-freeze: 0.0.1 events-listener: 1.1.0 glob: 10.5.0 json-ptr: 3.1.1 json-schema-traverse: 1.0.0 - lodash: 4.18.1 + lodash: 4.17.23 openapi3-ts: 3.2.0 promise-breaker: 6.0.0 - qs: 6.15.3 + qs: 6.15.0 raw-body: 2.5.3 - semver: 7.8.5 + semver: 7.7.4 transitivePeerDependencies: - supports-color exit@0.1.2: {} - exponential-backoff@3.1.3: - optional: true + exponential-backoff@3.1.3: {} - express-rate-limit@8.5.2(express@5.2.1): + express-rate-limit@7.5.1(express@5.2.1): dependencies: express: 5.2.1 - ip-address: 10.2.0 - express@4.22.2: + express-rate-limit@8.2.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.0.1 + + express@4.22.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.6 + body-parser: 1.20.4 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 @@ -15028,9 +15651,9 @@ snapshots: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.13 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.15.3 + qs: 6.14.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -15046,8 +15669,8 @@ snapshots: express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.1.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -15065,23 +15688,35 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.3 - range-parser: 1.3.0 + qs: 6.15.0 + range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.1.0 + type-is: 2.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color extend@3.0.2: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + extsprintf@1.3.0: {} eyes@0.1.8: {} + fast-content-type-parse@3.0.0: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -15104,9 +15739,9 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.3: {} + fast-uri@3.1.0: {} - fast-wrap-ansi@0.2.2: + fast-wrap-ansi@0.2.0: dependencies: fast-string-width: 3.0.2 @@ -15118,11 +15753,15 @@ snapshots: faye-websocket@0.11.4: dependencies: - websocket-driver: 0.7.5 + websocket-driver: 0.7.4 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.3 fecha@4.2.3: {} @@ -15135,6 +15774,12 @@ snapshots: dependencies: flat-cache: 3.2.0 + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + filesize@6.4.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -15184,21 +15829,22 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase-tools@15.23.0(@types/node@22.20.1)(bufferutil@4.1.0)(encoding@0.1.13): + firebase-tools@15.2.1(@types/node@22.19.11)(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.9.2): dependencies: + '@apphosting/build': 0.1.7(@types/node@22.19.11)(typescript@5.9.2) '@apphosting/common': 0.0.8 - '@electric-sql/pglite': 0.3.16 - '@electric-sql/pglite-tools': 0.2.21(@electric-sql/pglite@0.3.16) - '@google-cloud/cloud-sql-connector': 1.11.2 - '@google-cloud/pubsub': 5.3.1 - '@inquirer/prompts': 7.10.1(@types/node@22.20.1) - '@modelcontextprotocol/sdk': 1.29.0 + '@electric-sql/pglite': 0.3.15 + '@electric-sql/pglite-tools': 0.2.20(@electric-sql/pglite@0.3.15) + '@google-cloud/cloud-sql-connector': 1.9.0 + '@google-cloud/pubsub': 5.2.3 + '@inquirer/prompts': 7.10.1(@types/node@22.19.11) + '@modelcontextprotocol/sdk': 1.26.0 abort-controller: 3.0.0 - ajv: 8.20.0 + ajv: 8.18.0 ajv-formats: 3.0.1 archiver: 7.0.1 async-lock: 1.4.1 - body-parser: 1.20.6 + body-parser: 1.20.4 chokidar: 3.6.0 cjson: 0.3.3 cli-table3: 0.6.5 @@ -15209,118 +15855,126 @@ snapshots: cross-env: 7.0.3 cross-spawn: 7.0.6 csv-parse: 5.6.0 + deep-equal-in-any-order: 2.1.0 exegesis: 4.3.0 exegesis-express: 4.0.0 - express: 4.22.2 - form-data: 4.0.6 + express: 4.22.1 + filesize: 6.4.0 + form-data: 4.0.5 fs-extra: 10.1.0 fuzzy: 0.1.3 gaxios: 6.7.1(encoding@0.1.13) glob: 10.5.0 google-auth-library: 9.15.1(encoding@0.1.13) - ignore: 7.0.6 - js-yaml: 4.3.0 + ignore: 7.0.5 + js-yaml: 3.14.2 jsonwebtoken: 9.0.3 + leven: 3.1.0 libsodium-wrappers: 0.7.16 - lodash: 4.18.1 - lsofi: 2.0.0 + lodash: 4.17.23 + lsofi: 1.0.0 marked: 13.0.3 marked-terminal: 7.3.0(marked@13.0.3) mime: 2.6.0 - minimatch: 3.1.5 - morgan: 1.11.0 + minimatch: 3.1.2 + morgan: 1.10.1 node-fetch: 2.7.0(encoding@0.1.13) open: 6.4.0 ora: 5.4.1 - pg: 8.22.0 + p-limit: 3.1.0 + pg: 8.18.0 pg-gateway: 0.3.0-beta.4 pglite-2: '@electric-sql/pglite@0.2.17' portfinder: 1.0.38 progress: 2.0.3 proxy-agent: 6.5.0 retry: 0.13.1 - semver: 7.8.5 - sql-formatter: 15.8.2 + semver: 7.7.4 + sql-formatter: 15.7.1 stream-chain: 2.2.5 stream-json: 1.9.1 superstatic: 10.0.0(encoding@0.1.13) - tar: 7.5.20 - tcp-port-used: 1.0.3 - tmp: 0.2.7 + tcp-port-used: 1.0.2 + tmp: 0.2.5 triple-beam: 1.4.1 - undici: 6.27.0 - universal-analytics: 0.5.4 + universal-analytics: 0.5.3 update-notifier-cjs: 5.1.7(encoding@0.1.13) + uuid: 8.3.2 winston: 3.19.0 winston-transport: 4.9.0 - ws: 7.5.11(bufferutil@4.1.0) - yaml: 2.9.0 - zod: 4.4.3 + ws: 7.5.10(bufferutil@4.1.0) + yaml: 2.8.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) transitivePeerDependencies: - '@cfworker/json-schema' + - '@swc/core' + - '@swc/wasm' - '@types/node' - bare-abort-controller - - bare-buffer - bufferutil - encoding - pg-native - react-native-b4a - supports-color + - typescript - utf-8-validate - firebase@12.16.0: - dependencies: - '@firebase/ai': 2.13.1(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/analytics': 0.10.22(@firebase/app@0.15.1) - '@firebase/analytics-compat': 0.2.28(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/app': 0.15.1 - '@firebase/app-check': 0.12.0(@firebase/app@0.15.1) - '@firebase/app-check-compat': 0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/app-compat': 0.5.15 - '@firebase/app-types': 0.9.5 - '@firebase/auth': 1.13.3(@firebase/app@0.15.1) - '@firebase/auth-compat': 0.6.8(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/data-connect': 0.7.1(@firebase/app@0.15.1) - '@firebase/database': 1.1.3 - '@firebase/database-compat': 2.1.4 - '@firebase/firestore': 4.16.0(@firebase/app@0.15.1) - '@firebase/firestore-compat': 0.4.11(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/functions': 0.13.5(@firebase/app@0.15.1) - '@firebase/functions-compat': 0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/installations-compat': 0.2.22(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/messaging': 0.13.0(@firebase/app@0.15.1) - '@firebase/messaging-compat': 0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/performance': 0.7.12(@firebase/app@0.15.1) - '@firebase/performance-compat': 0.2.25(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/remote-config': 0.9.0(@firebase/app@0.15.1) - '@firebase/remote-config-compat': 0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/storage': 0.14.3(@firebase/app@0.15.1) - '@firebase/storage-compat': 0.4.3(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/util': 1.15.1 + firebase@12.8.0: + dependencies: + '@firebase/ai': 2.7.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/analytics': 0.10.19(@firebase/app@0.14.7) + '@firebase/analytics-compat': 0.2.25(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/app': 0.14.7 + '@firebase/app-check': 0.11.0(@firebase/app@0.14.7) + '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/app-compat': 0.5.7 + '@firebase/app-types': 0.9.3 + '@firebase/auth': 1.12.0(@firebase/app@0.14.7) + '@firebase/auth-compat': 0.6.2(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/data-connect': 0.3.12(@firebase/app@0.14.7) + '@firebase/database': 1.1.0 + '@firebase/database-compat': 2.1.0 + '@firebase/firestore': 4.10.0(@firebase/app@0.14.7) + '@firebase/firestore-compat': 0.4.4(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/functions': 0.13.1(@firebase/app@0.14.7) + '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/installations': 0.6.19(@firebase/app@0.14.7) + '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/messaging': 0.12.23(@firebase/app@0.14.7) + '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/performance': 0.7.9(@firebase/app@0.14.7) + '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/remote-config': 0.8.0(@firebase/app@0.14.7) + '@firebase/remote-config-compat': 0.2.21(@firebase/app-compat@0.5.7)(@firebase/app@0.14.7) + '@firebase/storage': 0.14.0(@firebase/app@0.14.7) + '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.7)(@firebase/app-types@0.9.3)(@firebase/app@0.14.7) + '@firebase/util': 1.13.0 transitivePeerDependencies: - '@react-native-async-storage/async-storage' flat-cache@3.2.0: dependencies: - flatted: 3.4.2 + flatted: 3.3.3 keyv: 4.5.4 rimraf: 3.0.2 flat@5.0.2: {} - flatted@3.4.2: {} + flatted@3.3.3: {} fn.name@1.1.0: {} - folder-hash@4.1.3(supports-color@10.2.2): + folder-hash@4.1.1(supports-color@10.2.2): dependencies: debug: 4.4.0(supports-color@10.2.2) - minimatch: 7.4.9 + minimatch: 7.4.6 transitivePeerDependencies: - supports-color - follow-redirects@1.16.0: {} + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3(supports-color@10.2.2) for-each@0.3.5: dependencies: @@ -15339,12 +15993,12 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 - form-data@4.0.6: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.4 + hasown: 2.0.2 mime-types: 2.1.35 formdata-polyfill@4.0.10: @@ -15361,16 +16015,18 @@ snapshots: fresh@2.0.0: {} + from@0.1.7: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.1 + jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@11.3.6: + fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.1 + jsonfile: 6.2.0 universalify: 2.0.1 fs-extra@8.1.0: @@ -15379,6 +16035,10 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.2 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -15386,17 +16046,14 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.2.0: + function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 - es-define-property: 1.0.1 - es-errors: 1.3.0 + define-properties: 1.2.1 functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 is-callable: 1.2.7 - is-document.all: 1.0.0 functions-have-names@1.2.3: {} @@ -15422,14 +16079,6 @@ snapshots: transitivePeerDependencies: - supports-color - gaxios@7.2.0(supports-color@10.2.2): - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6(supports-color@10.2.2) - node-fetch: 3.3.2 - transitivePeerDependencies: - - supports-color - gaze@1.1.3: dependencies: globule: 1.3.4 @@ -15444,14 +16093,6 @@ snapshots: - supports-color gcp-metadata@8.1.2(supports-color@10.2.2): - dependencies: - gaxios: 7.2.0(supports-color@10.2.2) - google-logging-utils: 1.1.4 - json-bigint: 1.0.0 - transitivePeerDependencies: - - supports-color - - gcp-metadata@8.1.3(supports-color@10.2.2): dependencies: gaxios: 7.1.3(supports-color@10.2.2) google-logging-utils: 1.1.3 @@ -15465,25 +16106,29 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.6.0: {} + get-east-asian-width@1.4.0: {} get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.4 + hasown: 2.0.2 math-intrinsics: 1.1.0 get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 get-stream@6.0.1: {} @@ -15493,9 +16138,13 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + get-uri@6.0.5: dependencies: - basic-ftp: 5.3.1 + basic-ftp: 5.1.0 data-uri-to-buffer: 6.0.2 debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: @@ -15505,6 +16154,14 @@ snapshots: dependencies: assert-plus: 1.0.0 + git-raw-commits@5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1): + dependencies: + '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) + meow: 13.2.0 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -15525,12 +16182,14 @@ snapshots: dependencies: tslib: 2.8.1 + glob-to-regexp@0.4.1: {} + glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 + minimatch: 9.0.5 + minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -15538,22 +16197,22 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.2.3 - minimatch: 10.2.5 - minipass: 7.1.3 + minimatch: 10.2.0 + minipass: 7.1.2 package-json-from-dist: 1.0.1 - path-scurry: 2.0.2 + path-scurry: 2.0.1 - glob@13.0.6: + glob@13.0.3: dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 + minimatch: 10.2.0 + minipass: 7.1.2 + path-scurry: 2.0.1 glob@5.0.15: dependencies: inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.5 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 @@ -15562,7 +16221,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.5 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 @@ -15571,7 +16230,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.5 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 @@ -15617,28 +16276,17 @@ snapshots: globule@1.3.4: dependencies: glob: 7.1.7 - lodash: 4.18.1 + lodash: 4.17.23 minimatch: 3.0.8 google-auth-library@10.5.0(supports-color@10.2.2): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.2.0(supports-color@10.2.2) - gcp-metadata: 8.1.3(supports-color@10.2.2) - google-logging-utils: 1.1.4 - gtoken: 8.0.0(supports-color@10.2.2) - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - - google-auth-library@10.9.0(supports-color@10.2.2): - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 7.2.0(supports-color@10.2.2) + gaxios: 7.1.3(supports-color@10.2.2) gcp-metadata: 8.1.2(supports-color@10.2.2) google-logging-utils: 1.1.3 + gtoken: 8.0.0(supports-color@10.2.2) jws: 4.0.1 transitivePeerDependencies: - supports-color @@ -15655,18 +16303,18 @@ snapshots: - encoding - supports-color - google-gax@5.0.7(supports-color@10.2.2): + google-gax@5.0.6(supports-color@10.2.2): dependencies: - '@grpc/grpc-js': 1.14.4 - '@grpc/proto-loader': 0.8.1 + '@grpc/grpc-js': 1.14.3 + '@grpc/proto-loader': 0.8.0 duplexify: 4.1.3 google-auth-library: 10.5.0(supports-color@10.2.2) google-logging-utils: 1.1.3 node-fetch: 3.3.2 object-hash: 3.0.0 proto3-json-serializer: 3.0.4 - protobufjs: 8.7.1 - retry-request: 8.0.3(supports-color@10.2.2) + protobufjs: 7.5.4 + retry-request: 8.0.2(supports-color@10.2.2) rimraf: 5.0.10 transitivePeerDependencies: - supports-color @@ -15675,15 +16323,12 @@ snapshots: google-logging-utils@1.1.3: {} - google-logging-utils@1.1.4: {} - - googleapis-common@8.0.2: + googleapis-common@8.0.1: dependencies: extend: 3.0.2 gaxios: 7.1.3(supports-color@10.2.2) google-auth-library: 10.5.0(supports-color@10.2.2) - google-logging-utils: 1.1.3 - qs: 6.15.3 + qs: 6.15.0 url-template: 2.0.8 transitivePeerDependencies: - supports-color @@ -15694,17 +16339,17 @@ snapshots: graceful-fs@4.2.11: {} - graphql-tag@2.12.7(graphql@16.14.2): + graphql-tag@2.12.6(graphql@16.12.0): dependencies: - graphql: 16.14.2 + graphql: 16.12.0 tslib: 2.8.1 - graphql@16.14.2: {} + graphql@16.12.0: {} grpc-gcp@1.0.1: dependencies: - '@grpc/grpc-js': 1.14.4 - protobufjs: 8.7.1 + '@grpc/grpc-js': 1.14.3 + protobufjs: 7.4.0 gtoken@7.1.0(encoding@0.1.13): dependencies: @@ -15716,14 +16361,14 @@ snapshots: gtoken@8.0.0(supports-color@10.2.2): dependencies: - gaxios: 7.2.0(supports-color@10.2.2) + gaxios: 7.1.3(supports-color@10.2.2) jws: 4.0.1 transitivePeerDependencies: - supports-color handle-thing@2.0.1: {} - handlebars@4.7.9: + handlebars@4.7.8: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -15736,7 +16381,7 @@ snapshots: har-validator@5.1.5: dependencies: - ajv: 6.15.0 + ajv: 6.12.6 har-schema: 2.0.0 hard-rejection@2.1.0: {} @@ -15769,7 +16414,7 @@ snapshots: has-yarn@2.1.0: {} - hasown@2.0.4: + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -15818,11 +16463,7 @@ snapshots: highlight.js@11.11.1: {} - hono@4.12.30: {} - - hosted-git-info@10.1.1: - dependencies: - lru-cache: 11.5.2 + hono@4.11.9: {} hosted-git-info@2.8.9: {} @@ -15830,6 +16471,14 @@ snapshots: dependencies: lru-cache: 6.0.0 + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + hosted-git-info@9.0.2: + dependencies: + lru-cache: 11.2.6 + hpack.js@2.1.6: dependencies: inherits: 2.0.4 @@ -15863,6 +16512,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + http-cache-semantics@4.2.0: {} + http-deceiver@1.2.7: {} http-errors@1.8.1: @@ -15881,21 +16532,29 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-link-header@1.1.4: {} + http-link-header@1.1.3: {} http-parser-js@0.5.10: {} - http-proxy-agent@7.0.2(supports-color@10.2.2): + http-proxy-agent@5.0.0(supports-color@10.2.2): + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2(supports-color@10.2.2) + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.10(@types/express@4.17.25): + http-proxy-middleware@2.0.9(@types/express@4.17.25): dependencies: '@types/http-proxy': 1.17.17 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.4.3) is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.8 @@ -15904,20 +16563,21 @@ snapshots: transitivePeerDependencies: - debug - http-proxy-middleware@4.2.0: + http-proxy-middleware@3.0.5: dependencies: + '@types/http-proxy': 1.17.17 debug: 4.4.3(supports-color@10.2.2) - httpxy: 0.5.5 + http-proxy: 1.18.1(debug@4.4.3) is-glob: 4.0.3 - is-plain-obj: 4.1.0 + is-plain-object: 5.0.0 micromatch: 4.0.8 transitivePeerDependencies: - supports-color - http-proxy@1.18.1: + http-proxy@1.18.1(debug@4.4.3): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.16.0 + follow-redirects: 1.15.11(debug@4.4.3) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -15935,9 +16595,9 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@10.2.2): dependencies: - agent-base: 6.0.2 + agent-base: 6.0.2(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -15949,17 +16609,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@9.1.0: - dependencies: - agent-base: 9.0.0 - debug: 4.4.3(supports-color@10.2.2) - proxy-agent-negotiate: 1.1.0 - transitivePeerDependencies: - - kerberos - - supports-color - - httpxy@0.5.5: {} - human-signals@2.1.0: {} husky@9.1.7: {} @@ -15974,21 +16623,25 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.3: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.19): + icss-utils@5.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.19 + postcss: 8.5.6 idb@7.1.1: {} ieee754@1.2.1: {} + ignore-walk@8.0.0: + dependencies: + minimatch: 10.2.0 + ignore@5.3.2: {} - ignore@7.0.6: {} + ignore@7.0.5: {} image-size@0.5.5: optional: true @@ -15997,7 +16650,7 @@ snapshots: immediate@3.0.6: {} - immutable@5.1.9: {} + immutable@5.1.4: {} import-fresh@3.3.1: dependencies: @@ -16006,8 +16659,8 @@ snapshots: import-in-the-middle@1.15.0: dependencies: - acorn: 8.17.0 - acorn-import-attributes: 1.9.5(acorn@8.17.0) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 @@ -16015,12 +16668,12 @@ snapshots: import-lazy@4.0.0: {} - import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} indent-string@4.0.0: {} + infer-owner@1.0.4: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -16032,14 +16685,16 @@ snapshots: ini@2.0.0: {} - install-artifact-from-github@1.7.0: + ini@6.0.0: {} + + install-artifact-from-github@1.4.0: optional: true internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.4 - side-channel: 1.1.1 + hasown: 2.0.2 + side-channel: 1.1.0 intl-messageformat@10.7.18: dependencies: @@ -16048,13 +16703,15 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 - ip-address@10.2.0: {} + ip-address@10.0.1: {} - ip-regex@2.1.0: {} + ip-address@10.1.0: {} + + ip-regex@4.3.0: {} ipaddr.js@1.9.1: {} - ipaddr.js@2.4.0: {} + ipaddr.js@2.3.0: {} is-alphabetical@1.0.4: {} @@ -16065,7 +16722,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -16092,6 +16749,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-buffer@1.1.6: {} + is-buffer@2.0.5: {} is-callable@1.2.7: {} @@ -16100,9 +16759,9 @@ snapshots: dependencies: ci-info: 2.0.0 - is-core-module@2.16.2: + is-core-module@2.16.1: dependencies: - hasown: 2.0.4 + hasown: 2.0.2 is-data-view@1.0.2: dependencies: @@ -16121,10 +16780,6 @@ snapshots: is-docker@3.0.0: {} - is-document.all@1.0.0: - dependencies: - call-bound: 1.0.4 - is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -16135,7 +16790,7 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.6.0 + get-east-asian-width: 1.4.0 is-generator-function@1.1.2: dependencies: @@ -16176,7 +16831,7 @@ snapshots: is-negative-zero@2.0.3: {} - is-network-error@1.3.2: {} + is-network-error@1.3.0: {} is-node-process@1.2.0: {} @@ -16187,6 +16842,10 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-number@2.1.0: + dependencies: + kind-of: 3.2.2 + is-number@7.0.0: {} is-obj@2.0.0: {} @@ -16207,8 +16866,6 @@ snapshots: is-plain-obj@3.0.0: {} - is-plain-obj@4.1.0: {} - is-plain-object@2.0.4: dependencies: isobject: 3.0.1 @@ -16219,14 +16876,14 @@ snapshots: is-reference@1.2.1: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 is-regex@1.2.1: dependencies: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.4 + hasown: 2.0.2 is-running@2.1.0: {} @@ -16253,7 +16910,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.22 + which-typed-array: 1.1.20 is-typedarray@1.0.0: {} @@ -16278,7 +16935,7 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-what@4.1.16: {} + is-what@3.14.1: {} is-wsl@1.1.0: {} @@ -16292,10 +16949,10 @@ snapshots: is-yarn-global@0.3.0: {} - is2@2.0.1: + is2@2.0.9: dependencies: deep-is: 0.1.4 - ip-regex: 2.1.0 + ip-regex: 4.3.0 is-url: 1.2.4 is@3.3.2: {} @@ -16310,6 +16967,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.5: {} + isexe@4.0.0: {} isobject@3.0.1: {} @@ -16327,9 +16986,9 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: @@ -16337,11 +16996,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.8.5 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -16371,8 +17030,8 @@ snapshots: escodegen: 1.8.1 esprima: 2.7.3 glob: 5.0.15 - handlebars: 4.7.9 - js-yaml: 3.15.0 + handlebars: 4.7.8 + js-yaml: 3.14.2 mkdirp: 0.5.6 nopt: 3.0.6 once: 1.4.0 @@ -16391,15 +17050,23 @@ snapshots: dependencies: '@isaacs/cliui': 9.0.0 + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + jasmine-core@2.8.0: {} jasmine-core@4.6.1: {} - jasmine-core@6.3.0: {} + jasmine-core@6.0.0: {} + + jasmine-core@6.0.1: {} jasmine-reporters@2.5.2: dependencies: - '@xmldom/xmldom': 0.8.13 + '@xmldom/xmldom': 0.8.11 mkdirp: 1.0.4 jasmine-spec-reporter@7.0.0: @@ -16412,21 +17079,21 @@ snapshots: glob: 7.2.3 jasmine-core: 2.8.0 - jasmine@6.3.0: + jasmine@6.0.0: dependencies: '@jasminejs/reporters': 1.0.0 - glob: 13.0.6 - jasmine-core: 6.3.0 + glob: 13.0.3 + jasmine-core: 6.0.1 jasminewd2@2.2.0: {} jest-worker@27.5.1: dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@2.7.0: {} + jiti@2.6.1: {} jju@1.4.0: {} @@ -16436,22 +17103,20 @@ snapshots: url-join: 0.0.1 valid-url: 1.0.9 - jose@6.2.3: {} + jose@6.1.3: {} jpeg-js@0.4.4: {} js-library-detector@6.7.0: {} - js-tokens@10.0.0: {} - js-tokens@4.0.0: {} - js-yaml@3.15.0: + js-yaml@3.14.2: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.3.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -16469,6 +17134,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-parse-even-better-errors@5.0.0: {} + json-parse-helpfulerror@1.0.3: dependencies: jju: 1.4.0 @@ -16485,8 +17152,6 @@ snapshots: json-stringify-safe@5.0.1: {} - json-with-bigint@3.5.10: {} - json5@2.2.3: {} jsonc-parser@3.3.1: {} @@ -16495,12 +17160,14 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.2.1: + jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + jsonparse@1.3.1: {} + jsonwebtoken@9.0.3: dependencies: jws: 4.0.1 @@ -16512,7 +17179,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.8.5 + semver: 7.7.4 jsprim@1.4.2: dependencies: @@ -16544,7 +17211,7 @@ snapshots: karma-browserstack-launcher@1.6.0(karma@6.4.4(bufferutil@4.1.0)): dependencies: browserstack: 1.5.3 - browserstack-local: 1.5.13 + browserstack-local: 1.5.10 karma: 6.4.4(bufferutil@4.1.0) q: 1.5.1 transitivePeerDependencies: @@ -16561,7 +17228,7 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.2.0 - minimatch: 3.1.5 + minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -16579,7 +17246,7 @@ snapshots: dependencies: istanbul: 0.4.5 karma: 6.4.4(bufferutil@4.1.0) - lodash: 4.18.1 + lodash: 4.17.23 karma-source-map-support@1.4.0: dependencies: @@ -16592,7 +17259,7 @@ snapshots: karma@6.4.4(bufferutil@4.1.0): dependencies: '@colors/colors': 1.5.0 - body-parser: 1.20.6 + body-parser: 1.20.4 braces: 3.0.3 chokidar: 3.6.0 connect: 3.7.0 @@ -16600,21 +17267,21 @@ snapshots: dom-serialize: 2.2.1 glob: 7.2.3 graceful-fs: 4.2.11 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.4.3) isbinaryfile: 4.0.10 - lodash: 4.18.1 + lodash: 4.17.23 log4js: 6.9.1 mime: 2.6.0 - minimatch: 3.1.5 + minimatch: 3.1.2 mkdirp: 0.5.6 qjobs: 1.2.0 - range-parser: 1.3.0 + range-parser: 1.2.1 rimraf: 3.0.2 socket.io: 4.8.3(bufferutil@4.1.0) source-map: 0.6.1 - tmp: 0.2.7 + tmp: 0.2.5 ua-parser-js: 0.7.41 - yargs: 16.2.2 + yargs: 16.2.0 transitivePeerDependencies: - bufferutil - debug @@ -16623,7 +17290,11 @@ snapshots: keyv@4.5.4: dependencies: - json-buffer: 3.0.1 + json-buffer: 3.0.1 + + kind-of@3.2.2: + dependencies: + is-buffer: 1.1.6 kind-of@6.0.3: {} @@ -16633,10 +17304,10 @@ snapshots: kuler@2.0.0: {} - launch-editor@2.14.1: + launch-editor@2.12.0: dependencies: picocolors: 1.1.1 - shell-quote: 1.10.0 + shell-quote: 1.8.3 lazystream@1.0.1: dependencies: @@ -16644,26 +17315,28 @@ snapshots: legacy-javascript@0.0.1: {} - less-loader@13.0.0(less@4.6.7)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + less-loader@12.3.0(less@4.4.2)(webpack@5.105.0(esbuild@0.27.2)): dependencies: - '@types/less': 3.0.8 - less: 4.6.7 + less: 4.4.2 optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) - less@4.6.7: + less@4.4.2: dependencies: - copy-anything: 3.0.5 + copy-anything: 2.0.6 parse-node-version: 1.0.1 + tslib: 2.8.1 optionalDependencies: errno: 0.1.8 graceful-fs: 4.2.11 image-size: 0.5.5 - make-dir: 5.1.0 + make-dir: 2.1.0 mime: 1.6.0 - needle: 3.5.0 + needle: 3.3.1 source-map: 0.6.1 + leven@3.1.0: {} + levn@0.3.0: dependencies: prelude-ls: 1.1.2 @@ -16675,11 +17348,11 @@ snapshots: libsodium@0.7.16: {} - license-webpack-plugin@4.0.2(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + license-webpack-plugin@4.0.2(webpack@5.105.0(esbuild@0.27.2)): dependencies: - webpack-sources: 3.5.1 + webpack-sources: 3.3.4 optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) lie@3.3.0: dependencies: @@ -16692,7 +17365,7 @@ snapshots: connect-history-api-fallback: 1.6.0 connect-injector: 0.4.4 gaze: 1.1.3 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.4.3) morgan: 1.10.1 opener: 1.5.2 parseurl: 1.3.3 @@ -16700,7 +17373,7 @@ snapshots: serve-static: 1.14.2 spdy: 4.0.2 strip-json-comments: 3.1.1 - ws: 7.5.11(bufferutil@4.1.0) + ws: 7.5.10(bufferutil@4.1.0) transitivePeerDependencies: - bufferutil - debug @@ -16716,116 +17389,68 @@ snapshots: lighthouse-stack-packs@1.12.3: {} - lighthouse@13.4.0(bufferutil@4.1.0): + lighthouse@13.0.3(bufferutil@4.1.0): dependencies: - '@paulirish/trace_engine': 0.0.65 + '@paulirish/trace_engine': 0.0.61 '@sentry/node': 9.47.1 - axe-core: 4.12.1 + axe-core: 4.11.1 chrome-launcher: 1.2.1 configstore: 7.1.0 - csp_evaluator: 1.1.8 - devtools-protocol: 0.0.1625959 + csp_evaluator: 1.1.5 + devtools-protocol: 0.0.1527314 enquirer: 2.4.1 - http-link-header: 1.1.4 + http-link-header: 1.1.3 intl-messageformat: 10.7.18 jpeg-js: 0.4.4 js-library-detector: 6.7.0 lighthouse-logger: 2.0.2 lighthouse-stack-packs: 1.12.3 - lodash-es: 4.18.1 + lodash-es: 4.17.23 lookup-closest-locale: 6.2.0 open: 8.4.2 - puppeteer-core: 25.3.0(bufferutil@4.1.0) + puppeteer-core: 24.37.3(bufferutil@4.1.0) robots-parser: 3.0.1 speedline-core: 1.4.3 - third-party-web: 0.29.2 - tldts-icann: 7.4.8 - web-features: 3.32.0 - ws: 7.5.11(bufferutil@4.1.0) - yargs: 17.7.3 + third-party-web: 0.27.0 + tldts-icann: 7.0.23 + ws: 7.5.10(bufferutil@4.1.0) + yargs: 17.7.2 yargs-parser: 21.1.1 transitivePeerDependencies: + - bare-abort-controller + - bare-buffer - bufferutil - - proxy-agent + - react-native-b4a - supports-color - utf-8-validate - - yauzl - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 lines-and-columns@1.2.4: {} - listr2@10.2.2: + listr2@9.0.5: dependencies: - cli-truncate: 5.2.0 + cli-truncate: 5.1.1 + colorette: 2.0.20 eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 10.0.0 + wrap-ansi: 9.0.2 - lmdb@3.5.6: + lmdb@3.5.1: dependencies: '@harperfast/extended-iterable': 1.0.3 - msgpackr: 1.12.1 + msgpackr: 1.11.8 node-addon-api: 6.1.0 node-gyp-build-optional-packages: 5.2.2 ordered-binary: 1.6.1 weak-lru-cache: 1.2.2 optionalDependencies: - '@lmdb/lmdb-darwin-arm64': 3.5.6 - '@lmdb/lmdb-darwin-x64': 3.5.6 - '@lmdb/lmdb-linux-arm': 3.5.6 - '@lmdb/lmdb-linux-arm64': 3.5.6 - '@lmdb/lmdb-linux-x64': 3.5.6 - '@lmdb/lmdb-win32-arm64': 3.5.6 - '@lmdb/lmdb-win32-x64': 3.5.6 + '@lmdb/lmdb-darwin-arm64': 3.5.1 + '@lmdb/lmdb-darwin-x64': 3.5.1 + '@lmdb/lmdb-linux-arm': 3.5.1 + '@lmdb/lmdb-linux-arm64': 3.5.1 + '@lmdb/lmdb-linux-x64': 3.5.1 + '@lmdb/lmdb-win32-arm64': 3.5.1 + '@lmdb/lmdb-win32-x64': 3.5.1 optional: true load-json-file@4.0.0: @@ -16835,7 +17460,7 @@ snapshots: pify: 3.0.0 strip-bom: 3.0.0 - loader-runner@4.3.2: {} + loader-runner@4.3.1: {} loader-utils@2.0.4: dependencies: @@ -16853,7 +17478,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.18.1: {} + lodash-es@4.17.23: {} lodash._objecttypes@2.4.1: {} @@ -16881,11 +17506,13 @@ snapshots: lodash.isstring@4.0.1: {} + lodash.mapvalues@4.6.0: {} + lodash.once@4.1.1: {} lodash.snakecase@4.1.1: {} - lodash.template@4.18.1: + lodash.template@4.5.0: dependencies: lodash._reinterpolate: 3.0.0 lodash.templatesettings: 4.2.0 @@ -16896,7 +17523,7 @@ snapshots: lodash.truncate@4.4.2: {} - lodash@4.18.1: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -16913,14 +17540,14 @@ snapshots: ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 wrap-ansi: 9.0.2 log4js@6.9.1: dependencies: date-format: 4.0.14 debug: 4.4.3(supports-color@10.2.2) - flatted: 3.4.2 + flatted: 3.3.3 rfdc: 1.4.1 streamroller: 3.1.5 transitivePeerDependencies: @@ -16947,7 +17574,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.5.2: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -16959,7 +17586,10 @@ snapshots: lru-cache@7.18.3: {} - lsofi@2.0.0: {} + lsofi@1.0.0: + dependencies: + is-number: 2.1.0 + through2: 2.0.5 luxon@3.7.2: {} @@ -16967,23 +17597,44 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + make-dir@3.1.0: dependencies: semver: 6.3.1 make-dir@4.0.0: dependencies: - semver: 7.8.5 - - make-dir@5.1.0: - optional: true + semver: 7.7.4 make-error@1.3.6: {} + make-fetch-happen@15.0.3: + dependencies: + '@npmcli/agent': 4.0.0 + cacache: 20.0.3 + http-cache-semantics: 4.2.0 + minipass: 7.1.2 + minipass-fetch: 5.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 6.1.0 + promise-retry: 2.0.1 + ssri: 13.0.1 + transitivePeerDependencies: + - supports-color + map-obj@1.0.1: {} map-obj@4.3.0: {} + map-stream@0.1.0: {} + marked-terminal@7.3.0(marked@13.0.3): dependencies: ansi-escapes: 7.3.0 @@ -16999,7 +17650,7 @@ snapshots: marked@13.0.3: {} - marked@18.0.6: {} + marked@17.0.2: {} marky@1.3.0: {} @@ -17011,25 +17662,27 @@ snapshots: media-typer@1.1.0: {} - memfs@4.64.0(tslib@2.8.1): + memfs@4.56.10(tslib@2.8.1): dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.6.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 memorystream@0.3.1: {} + meow@13.2.0: {} + meow@9.0.0: dependencies: '@types/minimist': 1.2.5 @@ -17058,7 +17711,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.2 + picomatch: 2.3.1 mime-db@1.52.0: {} @@ -17082,41 +17735,45 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.10.2(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + mini-css-extract-plugin@2.10.0(webpack@5.105.0(esbuild@0.27.2)): dependencies: schema-utils: 4.3.3 - tapable: 2.3.3 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + tapable: 2.3.0 + webpack: 5.105.0(esbuild@0.27.2) minimalistic-assert@1.0.1: {} - minimatch@10.2.5: + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.1 + + minimatch@10.2.0: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.2 minimatch@3.0.8: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.12 - minimatch@3.1.5: + minimatch@3.1.2: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.12 - minimatch@5.1.9: + minimatch@5.1.6: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.0.2 - minimatch@6.2.3: + minimatch@6.2.0: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.0.2 - minimatch@7.4.9: + minimatch@7.4.6: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.0.2 - minimatch@9.0.9: + minimatch@9.0.5: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.0.2 minimist-options@4.1.0: dependencies: @@ -17126,22 +17783,39 @@ snapshots: minimist@1.2.8: {} - minimizer-webpack-plugin@5.6.1(esbuild@0.28.1)(postcss@8.5.16)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + minipass-collect@2.0.1: dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + minipass: 7.1.2 + + minipass-fetch@5.0.1: + dependencies: + minipass: 7.1.2 + minipass-sized: 2.0.0 + minizlib: 3.1.0 optionalDependencies: - esbuild: 0.28.1 - postcss: 8.5.16 + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@2.0.0: + dependencies: + minipass: 7.1.2 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 - minipass@7.1.3: {} + minipass@7.1.2: {} minizlib@3.1.0: dependencies: - minipass: 7.1.3 + minipass: 7.1.2 mitt@3.0.1: {} @@ -17151,13 +17825,11 @@ snapshots: mkdirp@1.0.4: {} - modern-tar@0.7.6: {} - module-details-from-path@1.0.4: {} moment@2.30.1: {} - moo@0.5.3: {} + moo@0.5.2: {} morgan@1.10.1: dependencies: @@ -17169,16 +17841,6 @@ snapshots: transitivePeerDependencies: - supports-color - morgan@1.11.0: - dependencies: - basic-auth: 2.0.1 - debug: 2.6.9 - depd: 2.0.0 - on-finished: 2.4.1 - on-headers: 1.1.0 - transitivePeerDependencies: - - supports-color - mrmime@2.0.1: {} ms@2.0.0: {} @@ -17187,21 +17849,21 @@ snapshots: ms@2.1.3: {} - msgpackr-extract@3.0.4: + msgpackr-extract@3.0.3: dependencies: node-gyp-build-optional-packages: 5.2.2 optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.12.1: + msgpackr@1.11.8: optionalDependencies: - msgpackr-extract: 3.0.4 + msgpackr-extract: 3.0.3 optional: true multicast-dns@7.2.5: @@ -17209,11 +17871,11 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 - multimatch@8.0.0: + multimatch@7.0.0: dependencies: array-differ: 4.0.0 array-union: 3.0.1 - minimatch: 10.2.5 + minimatch: 9.0.5 mute-stream@2.0.0: {} @@ -17225,22 +17887,22 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.28.0: + nan@2.25.0: optional: true - nanoid@3.3.16: {} + nanoid@3.3.11: {} nearley@2.20.1: dependencies: commander: 2.20.3 - moo: 0.5.3 + moo: 0.5.2 railroad-diagrams: 1.0.0 randexp: 0.4.6 - needle@3.5.0: + needle@3.3.1: dependencies: iconv-lite: 0.6.3 - sax: 1.6.0 + sax: 1.4.4 optional: true negotiator@0.6.3: {} @@ -17251,7 +17913,7 @@ snapshots: neo-async@2.6.2: {} - netmask@2.1.1: {} + netmask@2.0.2: {} nice-try@1.0.5: {} @@ -17259,9 +17921,9 @@ snapshots: dependencies: lower-case: 1.1.4 - nock@14.0.16: + nock@14.0.10: dependencies: - '@mswjs/interceptors': 0.41.9 + '@mswjs/interceptors': 0.39.8 json-stringify-safe: 5.0.1 propagate: 2.0.1 @@ -17299,53 +17961,108 @@ snapshots: node-gyp-build@4.8.4: {} - node-gyp@13.0.1: + node-gyp@12.2.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - nopt: 10.0.1 - proc-log: 7.0.0 - semver: 7.8.5 - tar: 7.5.20 - tinyglobby: 0.2.17 - undici: 8.7.0 - which: 7.0.0 - optional: true - - node-releases@2.0.51: {} + make-fetch-happen: 15.0.3 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + tar: 7.5.9 + tinyglobby: 0.2.15 + which: 6.0.1 + transitivePeerDependencies: + - supports-color - nopt@10.0.1: - dependencies: - abbrev: 5.0.0 - optional: true + node-releases@2.0.27: {} nopt@3.0.6: dependencies: abbrev: 1.0.9 + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.12 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.16.2 - semver: 7.8.5 + is-core-module: 2.16.1 + semver: 7.7.4 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} - npm-package-arg@14.0.0: + npm-bundled@5.0.0: + dependencies: + npm-normalize-package-bin: 5.0.0 + + npm-install-checks@6.3.0: + dependencies: + semver: 7.7.4 + + npm-install-checks@8.0.0: + dependencies: + semver: 7.7.4 + + npm-normalize-package-bin@3.0.1: {} + + npm-normalize-package-bin@5.0.0: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.7.4 + validate-npm-package-name: 5.0.1 + + npm-package-arg@13.0.2: dependencies: - hosted-git-info: 10.1.1 - proc-log: 7.0.0 - semver: 7.8.5 - validate-npm-package-name: 8.0.0 + hosted-git-info: 9.0.2 + proc-log: 6.1.0 + semver: 7.7.4 + validate-npm-package-name: 7.0.2 + + npm-packlist@10.0.3: + dependencies: + ignore-walk: 8.0.0 + proc-log: 6.1.0 + + npm-pick-manifest@11.0.3: + dependencies: + npm-install-checks: 8.0.0 + npm-normalize-package-bin: 5.0.0 + npm-package-arg: 13.0.2 + semver: 7.7.4 + + npm-pick-manifest@9.1.0: + dependencies: + npm-install-checks: 6.3.0 + npm-normalize-package-bin: 3.0.1 + npm-package-arg: 11.0.3 + semver: 7.7.4 + + npm-registry-fetch@19.1.1: + dependencies: + '@npmcli/redact': 4.0.0 + jsonparse: 1.3.1 + make-fetch-happen: 15.0.3 + minipass: 7.1.2 + minipass-fetch: 5.0.1 + minizlib: 3.1.0 + npm-package-arg: 13.0.2 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color npm-run-all@4.1.5: dependencies: @@ -17353,10 +18070,10 @@ snapshots: chalk: 2.4.2 cross-spawn: 6.0.6 memorystream: 0.3.1 - minimatch: 3.1.5 + minimatch: 3.1.2 pidtree: 0.3.1 read-pkg: 3.0.0 - shell-quote: 1.10.0 + shell-quote: 1.8.3 string.prototype.padend: 3.1.6 npm-run-path@4.0.1: @@ -17385,10 +18102,10 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -17396,8 +18113,6 @@ snapshots: obuf@1.1.2: {} - obug@2.1.3: {} - on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -17452,7 +18167,7 @@ snapshots: openapi3-ts@3.2.0: dependencies: - yaml: 2.9.0 + yaml: 2.8.2 opener@1.5.2: {} @@ -17477,7 +18192,7 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ora@9.4.1: + ora@9.2.0: dependencies: chalk: 5.6.2 cli-cursor: 5.0.0 @@ -17485,8 +18200,8 @@ snapshots: is-interactive: 2.0.0 is-unicode-supported: 2.1.0 log-symbols: 7.0.1 - stdin-discarder: 0.3.2 - string-width: 8.2.2 + stdin-discarder: 0.3.1 + string-width: 8.1.1 ordered-binary@1.6.1: optional: true @@ -17521,20 +18236,17 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@7.0.4: {} + p-queue@6.6.2: dependencies: eventemitter3: 4.0.7 p-timeout: 3.2.0 - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 - is-network-error: 1.3.2 + is-network-error: 1.3.0 retry: 0.13.1 p-throttle@7.0.0: {} @@ -17551,7 +18263,7 @@ snapshots: agent-base: 7.1.4 debug: 4.4.3(supports-color@10.2.2) get-uri: 6.0.5 - http-proxy-agent: 7.0.2(supports-color@10.2.2) + http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6(supports-color@10.2.2) pac-resolver: 7.0.1 socks-proxy-agent: 8.0.5 @@ -17561,10 +18273,32 @@ snapshots: pac-resolver@7.0.1: dependencies: degenerator: 5.0.1 - netmask: 2.1.1 + netmask: 2.0.2 package-json-from-dist@1.0.1: {} + pacote@21.1.0: + dependencies: + '@npmcli/git': 7.0.1 + '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/package-json': 7.0.4 + '@npmcli/promise-spawn': 9.0.1 + '@npmcli/run-script': 10.0.3 + cacache: 20.0.3 + fs-minipass: 3.0.3 + minipass: 7.1.2 + npm-package-arg: 13.0.2 + npm-packlist: 10.0.3 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.1 + proc-log: 6.1.0 + promise-retry: 2.0.1 + sigstore: 4.1.0 + ssri: 13.0.1 + tar: 7.5.9 + transitivePeerDependencies: + - supports-color + pako@1.0.11: {} param-case@2.1.1: @@ -17582,17 +18316,17 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse-node-version@1.0.1: {} - parse5-html-rewriting-stream@8.0.1: + parse5-html-rewriting-stream@8.0.0: dependencies: - entities: 8.0.0 - parse5: 8.0.1 + entities: 6.0.1 + parse5: 8.0.0 parse5-sax-parser: 8.0.0 parse5-htmlparser2-tree-adapter@6.0.1: @@ -17601,15 +18335,15 @@ snapshots: parse5-sax-parser@8.0.0: dependencies: - parse5: 8.0.1 + parse5: 8.0.0 parse5@5.1.1: {} parse5@6.0.1: {} - parse5@8.0.1: + parse5@8.0.0: dependencies: - entities: 8.0.0 + entities: 6.0.1 parseurl@1.3.3: {} @@ -17639,20 +18373,20 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.3 + minipass: 7.1.2 - path-scurry@2.0.2: + path-scurry@2.0.1: dependencies: - lru-cache: 11.5.2 - minipass: 7.1.3 + lru-cache: 11.2.6 + minipass: 7.1.2 - path-to-regexp@0.1.13: {} + path-to-regexp@0.1.12: {} path-to-regexp@1.9.0: dependencies: isarray: 0.0.1 - path-to-regexp@8.4.2: {} + path-to-regexp@8.3.0: {} path-type@3.0.0: dependencies: @@ -17660,22 +18394,28 @@ snapshots: path-type@4.0.0: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + + pend@1.2.0: {} + performance-now@2.1.0: {} - pg-cloudflare@1.4.0: + pg-cloudflare@1.3.0: optional: true - pg-connection-string@2.14.0: {} + pg-connection-string@2.11.0: {} pg-gateway@0.3.0-beta.4: {} pg-int8@1.0.1: {} - pg-pool@3.14.0(pg@8.22.0): + pg-pool@3.11.0(pg@8.18.0): dependencies: - pg: 8.22.0 + pg: 8.18.0 - pg-protocol@1.15.0: {} + pg-protocol@1.11.0: {} pg-types@2.2.0: dependencies: @@ -17685,15 +18425,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.22.0: + pg@8.18.0: dependencies: - pg-connection-string: 2.14.0 - pg-pool: 3.14.0(pg@8.22.0) - pg-protocol: 1.15.0 + pg-connection-string: 2.11.0 + pg-pool: 3.11.0(pg@8.18.0) + pg-protocol: 1.11.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.4.0 + pg-cloudflare: 1.3.0 pgpass@1.0.5: dependencies: @@ -17701,9 +18441,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.2: {} + picomatch@2.3.1: {} - picomatch@4.0.5: {} + picomatch@4.0.3: {} pidtree@0.3.1: {} @@ -17711,22 +18451,25 @@ snapshots: pify@3.0.0: {} + pify@4.0.1: + optional: true + pinkie-promise@2.0.1: dependencies: pinkie: 2.0.4 pinkie@2.0.4: {} - piscina@5.2.0: + piscina@5.1.4: optionalDependencies: '@napi-rs/nice': 1.1.1 pkce-challenge@5.0.1: {} - pkijs@3.4.0: + pkijs@3.3.3: dependencies: '@noble/hashes': 1.4.0 - asn1js: 3.0.10 + asn1js: 3.0.7 bytestreamjs: 2.0.1 pvtsutils: 1.3.6 pvutils: 1.1.5 @@ -17741,75 +18484,69 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.1(postcss@8.5.16)(typescript@6.0.2)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.105.0(esbuild@0.27.2)): dependencies: - cosmiconfig: 9.0.2(typescript@6.0.2) - jiti: 2.7.0 - postcss: 8.5.16 - semver: 7.8.5 + cosmiconfig: 9.0.0(typescript@5.9.2) + jiti: 2.6.1 + postcss: 8.5.6 + semver: 7.7.4 optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) transitivePeerDependencies: - typescript postcss-media-query-parser@0.2.3: {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.19): + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.19 + postcss: 8.5.6 - postcss-modules-local-by-default@4.2.0(postcss@8.5.19): + postcss-modules-local-by-default@4.2.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.19) - postcss: 8.5.19 - postcss-selector-parser: 7.1.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.19): + postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: - postcss: 8.5.19 - postcss-selector-parser: 7.1.4 + postcss: 8.5.6 + postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.19): + postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.19) - postcss: 8.5.19 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 postcss-resolve-nested-selector@0.1.6: {} - postcss-safe-parser@6.0.0(postcss@8.5.19): + postcss-safe-parser@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.5.19 + postcss: 8.5.6 - postcss-safe-parser@7.0.1(postcss@8.5.19): + postcss-safe-parser@7.0.1(postcss@8.5.6): dependencies: - postcss: 8.5.19 + postcss: 8.5.6 - postcss-scss@4.0.9(postcss@8.5.19): + postcss-scss@4.0.9(postcss@8.5.6): dependencies: - postcss: 8.5.19 + postcss: 8.5.6 - postcss-selector-parser@6.1.4: + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.4: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} - postcss@8.5.16: + postcss@8.5.6: dependencies: - nanoid: 3.3.16 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.19: - dependencies: - nanoid: 3.3.16 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -17827,9 +18564,11 @@ snapshots: prelude-ls@1.1.2: {} - prettier@3.9.5: {} + prettier@3.8.1: {} - proc-log@7.0.0: {} + proc-log@4.2.0: {} + + proc-log@6.1.0: {} process-nextick-args@2.0.1: {} @@ -17839,6 +18578,11 @@ snapshots: promise-breaker@6.0.0: {} + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -17854,10 +18598,36 @@ snapshots: proto3-json-serializer@3.0.4: dependencies: - protobufjs: 8.7.1 + protobufjs: 7.5.4 + + protobufjs@7.4.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.19.11 + long: 5.3.2 - protobufjs@8.7.1: - dependencies: + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.19.11 long: 5.3.2 protractor@7.0.0: @@ -17885,13 +18655,11 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent-negotiate@1.1.0: {} - proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@10.2.2) - http-proxy-agent: 7.0.2(supports-color@10.2.2) + http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6(supports-color@10.2.2) lru-cache: 7.18.3 pac-proxy-agent: 7.2.0 @@ -17905,6 +18673,10 @@ snapshots: prr@1.0.1: optional: true + ps-tree@1.2.0: + dependencies: + event-stream: 3.3.4 + psl@1.15.0: dependencies: punycode: 2.3.1 @@ -17914,6 +18686,11 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + pumpify@1.5.1: dependencies: duplexify: 3.7.1 @@ -17928,19 +18705,22 @@ snapshots: dependencies: escape-goat: 2.1.1 - puppeteer-core@25.3.0(bufferutil@4.1.0): + puppeteer-core@24.37.3(bufferutil@4.1.0): dependencies: - '@puppeteer/browsers': 3.0.6 - chromium-bidi: 16.0.1(devtools-protocol@0.0.1638949) - devtools-protocol: 0.0.1638949 - typed-query-selector: 2.12.2 - webdriver-bidi-protocol: 0.4.2 - ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + '@puppeteer/browsers': 2.12.1 + chromium-bidi: 14.0.0(devtools-protocol@0.0.1566079) + debug: 4.4.3(supports-color@10.2.2) + devtools-protocol: 0.0.1566079 + typed-query-selector: 2.12.0 + webdriver-bidi-protocol: 0.4.1 + ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: + - bare-abort-controller + - bare-buffer - bufferutil - - proxy-agent + - react-native-b4a + - supports-color - utf-8-validate - - yauzl pvtsutils@1.3.6: dependencies: @@ -17954,10 +18734,13 @@ snapshots: qjobs@1.2.0: {} - qs@6.15.3: + qs@6.14.2: dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 + side-channel: 1.1.0 + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 qs@6.5.5: {} @@ -17972,9 +18755,11 @@ snapshots: discontinuous-range: 1.0.0 ret: 0.1.15 - range-parser@1.2.1: {} + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 - range-parser@1.3.0: {} + range-parser@1.2.1: {} raw-body@2.5.3: dependencies: @@ -17987,7 +18772,7 @@ snapshots: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.3 + iconv-lite: 0.7.2 unpipe: 1.0.0 rc@1.2.8: @@ -17997,15 +18782,15 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - re2@1.26.0: + re2@1.23.3: dependencies: - install-artifact-from-github: 1.7.0 - nan: 2.28.0 - node-gyp: 13.0.1 + install-artifact-from-github: 1.4.0 + nan: 2.25.0 + node-gyp: 12.2.0 + transitivePeerDependencies: + - supports-color optional: true - re2js@0.4.3: {} - read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -18051,11 +18836,13 @@ snapshots: readdir-glob@1.1.3: dependencies: - minimatch: 5.1.9 + minimatch: 5.1.6 readdirp@3.6.0: dependencies: - picomatch: 2.3.2 + picomatch: 2.3.1 + + readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -18068,11 +18855,11 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 + es-abstract: 1.24.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -18087,7 +18874,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -18099,13 +18886,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.2 + regjsparser: 0.13.0 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 registry-auth-token@5.1.1: dependencies: - '@pnpm/npm-conf': 3.0.3 + '@pnpm/npm-conf': 3.0.2 registry-url@5.1.0: dependencies: @@ -18113,7 +18900,7 @@ snapshots: regjsgen@0.8.0: {} - regjsparser@0.13.2: + regjsparser@0.13.0: dependencies: jsesc: 3.1.0 @@ -18167,32 +18954,35 @@ snapshots: dependencies: debug: 4.4.3(supports-color@10.2.2) module-details-from-path: 1.0.4 - resolve: 1.22.12 + resolve: 1.22.11 transitivePeerDependencies: - supports-color require-main-filename@2.0.0: {} + requirejs@2.3.8: {} + requires-port@1.0.0: {} resolve-from@4.0.0: {} resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve-url-loader@5.0.0: dependencies: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.19 + postcss: 8.5.6 source-map: 0.6.1 resolve@1.1.7: {} - resolve@1.22.12: + resolve@1.22.11: dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -18208,13 +18998,15 @@ snapshots: ret@0.1.15: {} - retry-request@8.0.3(supports-color@10.2.2): + retry-request@8.0.2(supports-color@10.2.2): dependencies: extend: 3.0.2 - teeny-request: 10.1.3(supports-color@10.2.2) + teeny-request: 10.1.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color + retry@0.12.0: {} + retry@0.13.1: {} reusify@1.1.0: {} @@ -18235,74 +19027,69 @@ snapshots: robots-parser@3.0.1: {} - rolldown@1.1.5: + rolldown@1.0.0-rc.3: dependencies: - '@oxc-project/types': 0.139.0 - '@rolldown/pluginutils': 1.0.1 + '@oxc-project/types': 0.112.0 + '@rolldown/pluginutils': 1.0.0-rc.3 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 - - rollup-plugin-dts@6.4.1(rollup@4.62.2)(typescript@6.0.2): + '@rolldown/binding-android-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.3 + '@rolldown/binding-darwin-x64': 1.0.0-rc.3 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.3 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.3 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.3 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 + + rollup-plugin-dts@6.3.0(rollup@4.57.1)(typescript@5.9.2): dependencies: - '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.5.5 - convert-source-map: 2.0.0 magic-string: 0.30.21 - rollup: 4.62.2 - typescript: 6.0.2 + rollup: 4.57.1 + typescript: 5.9.2 optionalDependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 7.29.0 - rollup-plugin-sourcemaps2@0.5.8(@types/node@22.20.1)(rollup@4.62.2): + rollup-plugin-sourcemaps2@0.5.4(@types/node@22.19.11)(rollup@4.57.1): dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) - rollup: 4.62.2 + '@rollup/pluginutils': 5.2.0(rollup@4.57.1) + rollup: 4.57.1 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 - rollup@4.62.2: + rollup@4.57.1: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.62.2 - '@rollup/rollup-android-arm64': 4.62.2 - '@rollup/rollup-darwin-arm64': 4.62.2 - '@rollup/rollup-darwin-x64': 4.62.2 - '@rollup/rollup-freebsd-arm64': 4.62.2 - '@rollup/rollup-freebsd-x64': 4.62.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 - '@rollup/rollup-linux-arm-musleabihf': 4.62.2 - '@rollup/rollup-linux-arm64-gnu': 4.62.2 - '@rollup/rollup-linux-arm64-musl': 4.62.2 - '@rollup/rollup-linux-loong64-gnu': 4.62.2 - '@rollup/rollup-linux-loong64-musl': 4.62.2 - '@rollup/rollup-linux-ppc64-gnu': 4.62.2 - '@rollup/rollup-linux-ppc64-musl': 4.62.2 - '@rollup/rollup-linux-riscv64-gnu': 4.62.2 - '@rollup/rollup-linux-riscv64-musl': 4.62.2 - '@rollup/rollup-linux-s390x-gnu': 4.62.2 - '@rollup/rollup-linux-x64-gnu': 4.62.2 - '@rollup/rollup-linux-x64-musl': 4.62.2 - '@rollup/rollup-openbsd-x64': 4.62.2 - '@rollup/rollup-openharmony-arm64': 4.62.2 - '@rollup/rollup-win32-arm64-msvc': 4.62.2 - '@rollup/rollup-win32-ia32-msvc': 4.62.2 - '@rollup/rollup-win32-x64-gnu': 4.62.2 - '@rollup/rollup-win32-x64-msvc': 4.62.2 + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 router@2.2.0: @@ -18311,7 +19098,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.4.2 + path-to-regexp: 8.3.0 transitivePeerDependencies: - supports-color @@ -18323,9 +19110,9 @@ snapshots: rxjs-report-usage@1.0.6: dependencies: - '@babel/parser': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.0 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 bent: 7.3.12 chalk: 4.1.2 glob: 7.2.3 @@ -18333,18 +19120,18 @@ snapshots: transitivePeerDependencies: - supports-color - rxjs-tslint-rules@4.34.8(tslint@6.1.3(typescript@6.0.2))(typescript@6.0.2): + rxjs-tslint-rules@4.34.8(tslint@6.1.3(typescript@5.9.2))(typescript@5.9.2): dependencies: - '@phenomnomnominal/tsquery': 4.2.0(typescript@6.0.2) + '@phenomnomnominal/tsquery': 4.2.0(typescript@5.9.2) decamelize: 4.0.0 - resolve: 1.22.12 + resolve: 1.22.11 rxjs-report-usage: 1.0.6 - semver: 7.8.5 + semver: 7.7.4 tslib: 2.8.1 - tslint: 6.1.3(typescript@6.0.2) - tsutils: 3.21.0(typescript@6.0.2) - tsutils-etc: 1.4.2(tsutils@3.21.0(typescript@6.0.2))(typescript@6.0.2) - typescript: 6.0.2 + tslint: 6.1.3(typescript@5.9.2) + tsutils: 3.21.0(typescript@5.9.2) + tsutils-etc: 1.4.2(tsutils@3.21.0(typescript@5.9.2))(typescript@5.9.2) + typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -18356,9 +19143,9 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.4: + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -18385,15 +19172,25 @@ snapshots: safevalues@1.2.0: {} - sass-loader@17.0.0(sass@1.101.0)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + sass-loader@16.0.6(sass@1.97.3)(webpack@5.105.0(esbuild@0.27.2)): + dependencies: + neo-async: 2.6.2 + optionalDependencies: + sass: 1.97.3 + webpack: 5.105.0(esbuild@0.27.2) + + sass@1.97.2: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 optionalDependencies: - sass: 1.101.0 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + '@parcel/watcher': 2.5.6 - sass@1.101.0: + sass@1.97.3: dependencies: - chokidar: 5.0.0 - immutable: 5.1.9 + chokidar: 4.0.3 + immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.6 @@ -18404,14 +19201,14 @@ snapshots: transitivePeerDependencies: - supports-color - sax@1.6.0: {} + sax@1.4.4: {} schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) select-hose@2.0.0: {} @@ -18425,7 +19222,7 @@ snapshots: selfsigned@5.5.0: dependencies: '@peculiar/x509': 1.14.3 - pkijs: 3.4.0 + pkijs: 3.3.3 semver-diff@3.1.1: dependencies: @@ -18435,7 +19232,9 @@ snapshots: semver@6.3.1: {} - semver@7.8.5: {} + semver@7.7.3: {} + + semver@7.7.4: {} send@0.17.2: dependencies: @@ -18484,7 +19283,7 @@ snapshots: mime-types: 3.0.2 ms: 2.1.3 on-finished: 2.4.1 - range-parser: 1.3.0 + range-parser: 1.2.1 statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -18494,7 +19293,9 @@ snapshots: no-case: 2.3.2 upper-case-first: 1.1.2 - serialize-javascript@7.0.7: {} + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 serve-index@1.9.2: dependencies: @@ -18557,7 +19358,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 setimmediate@1.0.5: {} @@ -18579,7 +19380,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.10.0: {} + shell-quote@1.8.3: {} shelljs@0.10.0: dependencies: @@ -18588,7 +19389,7 @@ snapshots: shimmer@1.2.1: {} - side-channel-list@1.0.1: + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -18608,11 +19409,11 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.1: + side-channel@1.1.0: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.1 + side-channel-list: 1.0.0 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -18620,6 +19421,17 @@ snapshots: signal-exit@4.1.0: {} + sigstore@4.1.0: + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.1.0 + '@sigstore/protobuf-specs': 0.5.0 + '@sigstore/sign': 4.1.0 + '@sigstore/tuf': 4.0.1 + '@sigstore/verify': 3.1.0 + transitivePeerDependencies: + - supports-color + sisteransi@1.0.5: {} skin-tone@2.0.0: @@ -18639,12 +19451,7 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - slice-ansi@8.0.0: - dependencies: - ansi-styles: 6.2.3 - is-fullwidth-code-point: 5.1.0 - - slugify@1.6.9: {} + slugify@1.6.6: {} smart-buffer@4.2.0: {} @@ -18652,16 +19459,16 @@ snapshots: dependencies: no-case: 2.3.2 - socket.io-adapter@2.5.8(bufferutil@4.1.0): + socket.io-adapter@2.5.6(bufferutil@4.1.0): dependencies: debug: 4.4.3(supports-color@10.2.2) - ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.18.3(bufferutil@4.1.0) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.6: + socket.io-parser@4.2.5: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3(supports-color@10.2.2) @@ -18674,9 +19481,9 @@ snapshots: base64id: 2.0.0 cors: 2.8.6 debug: 4.4.3(supports-color@10.2.2) - engine.io: 6.6.9(bufferutil@4.1.0) - socket.io-adapter: 2.5.8(bufferutil@4.1.0) - socket.io-parser: 4.2.6 + engine.io: 6.6.5(bufferutil@4.1.0) + socket.io-adapter: 2.5.6(bufferutil@4.1.0) + socket.io-parser: 4.2.5 transitivePeerDependencies: - bufferutil - supports-color @@ -18686,28 +19493,32 @@ snapshots: dependencies: faye-websocket: 0.11.4 uuid: 8.3.2 - websocket-driver: 0.7.5 + websocket-driver: 0.7.4 socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@10.2.2) - socks: 2.8.9 + socks: 2.8.7 transitivePeerDependencies: - supports-color - socks@2.8.9: + socks@2.8.7: dependencies: - ip-address: 10.2.0 + ip-address: 10.1.0 smart-buffer: 4.2.0 + sort-any@2.0.0: + dependencies: + lodash: 4.17.23 + source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + source-map-loader@5.0.0(webpack@5.105.0(esbuild@0.27.2)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) source-map-support@0.4.18: dependencies: @@ -18734,16 +19545,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 + spdx-license-ids: 3.0.22 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 + spdx-license-ids: 3.0.22 - spdx-license-ids@3.0.23: {} + spdx-license-ids@3.0.22: {} spdx-license-list@2.1.0: {} @@ -18770,7 +19581,7 @@ snapshots: speedline-core@1.4.3: dependencies: - '@types/node': 22.20.1 + '@types/node': 22.19.11 image-ssim: 0.2.0 jpeg-js: 0.4.4 @@ -18785,9 +19596,13 @@ snapshots: split2@4.2.0: {} + split@0.3.3: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} - sql-formatter@15.8.2: + sql-formatter@15.7.1: dependencies: argparse: 2.0.1 nearley: 2.20.1 @@ -18806,7 +19621,11 @@ snapshots: ssri@10.0.5: dependencies: - minipass: 7.1.3 + minipass: 7.1.2 + + ssri@13.0.1: + dependencies: + minipass: 7.1.2 stack-trace@0.0.10: {} @@ -18814,7 +19633,7 @@ snapshots: statuses@2.0.2: {} - stdin-discarder@0.3.2: {} + stdin-discarder@0.3.1: {} stop-iteration-iterator@1.1.0: dependencies: @@ -18825,6 +19644,10 @@ snapshots: stream-chain@2.2.5: {} + stream-combiner@0.0.4: + dependencies: + duplexer: 0.1.2 + stream-events@1.0.5: dependencies: stubs: 3.0.0 @@ -18843,7 +19666,7 @@ snapshots: transitivePeerDependencies: - supports-color - streamx@2.28.0: + streamx@2.23.0: dependencies: events-universal: 1.0.1 fast-fifo: 1.3.2 @@ -18864,49 +19687,48 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 - string-width@8.2.2: + string-width@8.1.1: dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 string.prototype.padend@3.1.6: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 - string.prototype.trim@1.2.11: + string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 - safe-regex-test: 1.1.0 - string.prototype.trimend@1.0.10: + string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 define-properties: 1.2.1 - es-object-atoms: 1.1.2 + es-object-atoms: 1.1.1 string_decoder@1.1.1: dependencies: @@ -18932,7 +19754,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.2.0: + strip-ansi@7.1.2: dependencies: ansi-regex: 6.2.2 @@ -18960,7 +19782,7 @@ snapshots: stylelint@14.16.1: dependencies: - '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.4) + '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) balanced-match: 2.0.0 colord: 2.9.3 cosmiconfig: 7.1.0 @@ -18983,11 +19805,11 @@ snapshots: micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.19 + postcss: 8.5.6 postcss-media-query-parser: 0.2.3 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 6.0.0(postcss@8.5.19) - postcss-selector-parser: 6.1.4 + postcss-safe-parser: 6.0.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 @@ -19011,17 +19833,17 @@ snapshots: glob-slasher: 1.0.1 is-url: 1.2.4 join-path: 1.1.1 - lodash: 4.18.1 + lodash: 4.17.23 mime-types: 2.1.35 - minimatch: 6.2.3 - morgan: 1.11.0 + minimatch: 6.2.0 + morgan: 1.10.1 on-finished: 2.4.1 on-headers: 1.1.0 path-to-regexp: 1.9.0 router: 2.2.0 update-notifier-cjs: 5.1.7(encoding@0.1.13) optionalDependencies: - re2: 1.26.0 + re2: 1.23.3 transitivePeerDependencies: - encoding - supports-color @@ -19067,73 +19889,80 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.20.0 + ajv: 8.18.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 - tapable@2.3.3: {} + tapable@2.3.0: {} - tar-stream@3.2.0: + tar-fs@3.1.1: dependencies: - b4a: 1.8.1 - bare-fs: 4.7.4 - fast-fifo: 1.3.2 - streamx: 2.28.0 + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.5.4 + bare-path: 3.0.0 transitivePeerDependencies: - bare-abort-controller - bare-buffer - react-native-b4a - tar@7.5.20: + tar-stream@3.1.7: + dependencies: + b4a: 1.7.4 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + tar@7.5.9: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 - minipass: 7.1.3 + minipass: 7.1.2 minizlib: 3.1.0 yallist: 5.0.0 - tcp-port-used@1.0.3: + tcp-port-used@1.0.2: dependencies: debug: 4.3.1 - is2: 2.0.1 + is2: 2.0.9 transitivePeerDependencies: - supports-color - teeny-request@10.1.3(supports-color@10.2.2): + teeny-request@10.1.0(supports-color@10.2.2): dependencies: - http-proxy-agent: 7.0.2(supports-color@10.2.2) - https-proxy-agent: 7.0.6(supports-color@10.2.2) + http-proxy-agent: 5.0.0(supports-color@10.2.2) + https-proxy-agent: 5.0.1(supports-color@10.2.2) node-fetch: 3.3.2 stream-events: 1.0.5 transitivePeerDependencies: - supports-color - teex@1.0.1: - dependencies: - streamx: 2.28.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - terser@5.48.0: + terser-webpack-plugin@5.3.16(esbuild@0.27.2)(webpack@5.105.0(esbuild@0.27.2)): dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 - commander: 2.20.3 - source-map-support: 0.5.21 + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.46.0 + webpack: 5.105.0(esbuild@0.27.2) + optionalDependencies: + esbuild: 0.27.2 - terser@5.49.0: + terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 text-decoder@1.2.7: dependencies: - b4a: 1.8.1 + b4a: 1.7.4 transitivePeerDependencies: - react-native-b4a @@ -19147,11 +19976,13 @@ snapshots: dependencies: any-promise: 1.3.0 - thingies@2.6.0(tslib@2.8.1): + thingies@2.5.0(tslib@2.8.1): dependencies: tslib: 2.8.1 - third-party-web@0.29.2: {} + third-party-web@0.27.0: {} + + third-party-web@0.29.0: {} through2@2.0.5: dependencies: @@ -19162,40 +19993,42 @@ snapshots: dependencies: readable-stream: 3.6.2 + through@2.3.8: {} + thunky@1.1.0: {} tinyglobby@0.2.12: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 - tinyglobby@0.2.17: + tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 title-case@2.1.1: dependencies: no-case: 2.3.2 upper-case: 1.1.3 - tldts-core@7.4.8: {} + tldts-core@7.0.23: {} - tldts-icann@7.4.8: + tldts-icann@7.0.23: dependencies: - tldts-core: 7.4.8 + tldts-core: 7.0.23 tmp@0.0.30: dependencies: os-tmpdir: 1.0.2 - tmp@0.2.7: {} + tmp@0.2.5: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - toad-cache@3.7.4: {} + toad-cache@3.7.0: {} toidentifier@1.0.1: {} @@ -19206,7 +20039,7 @@ snapshots: toxic@1.0.1: dependencies: - lodash: 4.18.1 + lodash: 4.17.23 tr46@0.0.3: {} @@ -19222,74 +20055,80 @@ snapshots: trough@1.0.5: {} - ts-node@10.9.2(@types/node@22.20.1)(typescript@6.0.2): + ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.20.1 - acorn: 8.17.0 - acorn-walk: 8.3.5 + '@types/node': 22.19.11 + acorn: 8.15.0 + acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.4 make-error: 1.3.6 - typescript: 6.0.2 + typescript: 5.9.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsec@0.2.9(@bazel/bazelisk@1.28.1)(typescript@6.0.2): + tsec@0.2.9(@bazel/bazelisk@1.26.0)(typescript@5.9.2): dependencies: - '@bazel/bazelisk': 1.28.1 + '@bazel/bazelisk': 1.26.0 glob: 11.1.0 - minimatch: 10.2.5 - typescript: 6.0.2 + minimatch: 10.2.0 + typescript: 5.9.2 + + tsickle@0.46.3(typescript@5.9.2): + dependencies: + '@types/minimist': 1.2.5 + typescript: 5.9.2 tslib@1.14.1: {} tslib@2.8.1: {} - tslint@6.1.3(typescript@6.0.2): + tslint@6.1.3(typescript@5.9.2): dependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 7.29.0 builtin-modules: 1.1.1 chalk: 2.4.2 commander: 2.20.3 diff: 4.0.4 glob: 7.2.3 - js-yaml: 3.15.0 - minimatch: 3.1.5 + js-yaml: 3.14.2 + minimatch: 3.1.2 mkdirp: 0.5.6 - resolve: 1.22.12 + resolve: 1.22.11 semver: 5.7.2 tslib: 1.14.1 - tsutils: 2.29.0(typescript@6.0.2) - typescript: 6.0.2 + tsutils: 2.29.0(typescript@5.9.2) + typescript: 5.9.2 tsscmp@1.0.6: {} - tsutils-etc@1.4.2(tsutils@3.21.0(typescript@6.0.2))(typescript@6.0.2): + tsutils-etc@1.4.2(tsutils@3.21.0(typescript@5.9.2))(typescript@5.9.2): dependencies: '@types/yargs': 17.0.35 - tsutils: 3.21.0(typescript@6.0.2) - typescript: 6.0.2 - yargs: 17.7.3 + tsutils: 3.21.0(typescript@5.9.2) + typescript: 5.9.2 + yargs: 17.7.2 - tsutils@2.29.0(typescript@6.0.2): + tsutils@2.29.0(typescript@5.9.2): dependencies: tslib: 1.14.1 - typescript: 6.0.2 + typescript: 5.9.2 - tsutils@3.21.0(typescript@6.0.2): + tsutils@3.21.0(typescript@5.9.2): dependencies: tslib: 1.14.1 - typescript: 6.0.2 + typescript: 5.9.2 - tsx@4.23.1: + tsx@4.21.0: dependencies: - esbuild: 0.28.1 + esbuild: 0.27.3 + get-tsconfig: 4.13.6 optionalDependencies: fsevents: 2.3.3 @@ -19297,6 +20136,14 @@ snapshots: dependencies: tslib: 1.14.1 + tuf-js@4.1.0: + dependencies: + '@tufjs/models': 4.1.0 + debug: 4.4.3(supports-color@10.2.2) + make-fetch-happen: 15.0.3 + transitivePeerDependencies: + - supports-color + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -19324,9 +20171,9 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.1.0: + type-is@2.0.1: dependencies: - content-type: 2.0.0 + content-type: 1.0.5 media-typer: 1.1.0 mime-types: 3.0.2 @@ -19338,7 +20185,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -19347,16 +20194,16 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.8: + typed-array-length@1.0.7: dependencies: - call-bind: 1.0.9 + call-bind: 1.0.8 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 @@ -19367,13 +20214,13 @@ snapshots: typed-graphqlify@3.1.6: {} - typed-query-selector@2.12.2: {} + typed-query-selector@2.12.0: {} typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 - typescript@6.0.2: {} + typescript@5.9.2: {} ua-parser-js@0.7.41: {} @@ -19391,12 +20238,11 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.18.2: {} + undici-types@7.16.0: {} - undici@6.27.0: {} + undici@6.23.0: {} - undici@8.7.0: - optional: true + undici@7.20.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -19422,6 +20268,14 @@ snapshots: vfile: 3.0.1 x-is-string: 0.1.0 + unique-filename@5.0.0: + dependencies: + unique-slug: 6.0.0 + + unique-slug@6.0.0: + dependencies: + imurmurhash: 0.1.4 + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -19434,10 +20288,10 @@ snapshots: dependencies: '@types/unist': 3.0.3 - universal-analytics@0.5.4: + universal-analytics@0.5.3: dependencies: debug: 4.4.3(supports-color@10.2.2) - uuid: 14.0.1 + uuid: 8.3.2 transitivePeerDependencies: - supports-color @@ -19451,9 +20305,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.6): + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: - browserslist: 4.28.6 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -19472,7 +20326,7 @@ snapshots: pupa: 2.1.1 registry-auth-token: 5.1.1 registry-url: 5.1.0 - semver: 7.8.5 + semver: 7.7.4 semver-diff: 3.1.1 xdg-basedir: 4.0.0 transitivePeerDependencies: @@ -19504,8 +20358,6 @@ snapshots: utils-merge@1.0.1: {} - uuid@14.0.1: {} - uuid@3.4.0: {} uuid@8.3.2: {} @@ -19523,7 +20375,9 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validate-npm-package-name@8.0.0: {} + validate-npm-package-name@5.0.1: {} + + validate-npm-package-name@7.0.2: {} validate.js@0.12.0: {} @@ -19551,33 +20405,34 @@ snapshots: unist-util-stringify-position: 1.1.2 vfile-message: 1.1.1 - vite@8.1.4(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.7)(sass@1.101.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): + vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.5 - postcss: 8.5.19 - rolldown: 1.1.5 - tinyglobby: 0.2.17 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.20.1 - esbuild: 0.28.1 + '@types/node': 22.19.11 fsevents: 2.3.3 - jiti: 2.7.0 - less: 4.6.7 - sass: 1.101.0 - terser: 5.49.0 - tsx: 4.23.1 - yaml: 2.9.0 + jiti: 2.6.1 + less: 4.4.2 + sass: 1.97.3 + terser: 5.46.0 + tsx: 4.21.0 + yaml: 2.8.2 void-elements@2.0.1: {} - vrsource-tslint-rules@6.0.0(tslint@6.1.3(typescript@6.0.2))(typescript@6.0.2): + vrsource-tslint-rules@6.0.0(tslint@6.1.3(typescript@5.9.2))(typescript@5.9.2): dependencies: - tslint: 6.1.3(typescript@6.0.2) - typescript: 6.0.2 + tslint: 6.1.3(typescript@5.9.2) + typescript: 5.9.2 - watchpack@2.5.2: + watchpack@2.5.1: dependencies: + glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 wbuf@1.7.3: @@ -19591,15 +20446,13 @@ snapshots: weak-lru-cache@1.2.2: optional: true - web-features@3.32.0: {} - web-namespaces@1.1.4: {} web-streams-polyfill@3.3.3: {} web-vitals@4.2.4: {} - webdriver-bidi-protocol@0.4.2: {} + webdriver-bidi-protocol@0.4.1: {} webdriver-js-extender@2.1.0: dependencies: @@ -19608,7 +20461,7 @@ snapshots: webdriver-manager@12.1.9: dependencies: - adm-zip: 0.5.18 + adm-zip: 0.5.16 chalk: 1.1.3 del: 2.2.2 glob: 7.2.3 @@ -19622,52 +20475,40 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)): dependencies: colorette: 2.0.20 - memfs: 4.64.0(tslib@2.8.1) - mime-types: 3.0.2 - on-finished: 2.4.1 - range-parser: 1.3.0 - schema-utils: 4.3.3 - optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) - transitivePeerDependencies: - - tslib - - webpack-dev-middleware@8.0.3(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): - dependencies: - memfs: 4.64.0(tslib@2.8.1) + memfs: 4.56.10(tslib@2.8.1) mime-types: 3.0.2 on-finished: 2.4.1 - range-parser: 1.3.0 + range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.6(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + webpack-dev-server@5.2.3(bufferutil@4.1.0)(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 '@types/express': 4.17.25 - '@types/express-serve-static-core': 4.19.9 + '@types/express-serve-static-core': 4.19.8 '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 '@types/ws': 8.18.1 ansi-html-community: 0.0.8 - bonjour-service: 1.4.3 + bonjour-service: 1.3.0 chokidar: 3.6.0 colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.22.2 + express: 4.22.1 graceful-fs: 4.2.11 - http-proxy-middleware: 2.0.10(@types/express@4.17.25) - ipaddr.js: 2.4.0 - launch-editor: 2.14.1 + http-proxy-middleware: 2.0.9(@types/express@4.17.25) + ipaddr.js: 2.3.0 + launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 schema-utils: 4.3.3 @@ -19675,10 +20516,10 @@ snapshots: serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) - ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) + ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) transitivePeerDependencies: - bufferutil - debug @@ -19692,52 +20533,46 @@ snapshots: flat: 5.0.2 wildcard: 2.0.1 - webpack-sources@3.5.1: {} + webpack-sources@3.3.4: {} - webpack-subresource-integrity@5.1.0(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)): + webpack-subresource-integrity@5.1.0(webpack@5.105.0(esbuild@0.27.2)): dependencies: typed-assert: 1.0.9 - webpack: 5.108.4(esbuild@0.28.1)(postcss@8.5.16) + webpack: 5.105.0(esbuild@0.27.2) - webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16): + webpack@5.105.0(esbuild@0.27.2): dependencies: - '@types/estree': 1.0.9 + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.17.0 - acorn-import-phases: 1.0.4(acorn@8.17.0) - browserslist: 4.28.6 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.2 - es-module-lexer: 2.3.1 + enhanced-resolve: 5.19.0 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 + glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - loader-runner: 4.3.2 - mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(esbuild@0.28.1)(postcss@8.5.16)(webpack@5.108.4(esbuild@0.28.1)(postcss@8.5.16)) + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 - tapable: 2.3.3 - watchpack: 2.5.2 - webpack-sources: 3.5.1 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(esbuild@0.27.2)(webpack@5.105.0(esbuild@0.27.2)) + watchpack: 2.5.1 + webpack-sources: 3.3.4 transitivePeerDependencies: - - '@minify-html/node' - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - esbuild - - html-minifier-terser - - lightningcss - - postcss - uglify-js - websocket-driver@0.7.5: + websocket-driver@0.7.4: dependencies: http-parser-js: 0.5.10 safe-buffer: 5.2.1 @@ -19765,7 +20600,7 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 - function.prototype.name: 1.2.0 + function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -19776,7 +20611,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.22 + which-typed-array: 1.1.20 which-collection@1.0.2: dependencies: @@ -19787,10 +20622,10 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.22: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.9 + call-bind: 1.0.8 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -19809,7 +20644,11 @@ snapshots: dependencies: isexe: 2.0.0 - which@7.0.0: + which@6.0.0: + dependencies: + isexe: 3.1.5 + + which@6.0.1: dependencies: isexe: 4.0.0 @@ -19852,12 +20691,6 @@ snapshots: wordwrap@1.0.0: {} - wrap-ansi@10.0.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 8.2.2 - strip-ansi: 7.2.0 - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -19874,13 +20707,13 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.2.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -19896,11 +20729,15 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@7.5.11(bufferutil@4.1.0): + ws@7.5.10(bufferutil@4.1.0): optionalDependencies: bufferutil: 4.1.0 - ws@8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + ws@8.18.3(bufferutil@4.1.0): + optionalDependencies: + bufferutil: 4.1.0 + + ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: bufferutil: 4.1.0 utf-8-validate: 6.0.6 @@ -19924,7 +20761,7 @@ snapshots: xml2js@0.4.23: dependencies: - sax: 1.6.0 + sax: 1.4.4 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -19941,9 +20778,9 @@ snapshots: yallist@5.0.0: {} - yaml@1.10.3: {} + yaml@1.10.2: {} - yaml@2.9.0: {} + yaml@2.8.2: {} yargs-parser@18.1.3: dependencies: @@ -19970,7 +20807,7 @@ snapshots: y18n: 4.0.3 yargs-parser: 18.1.3 - yargs@16.2.2: + yargs@16.2.0: dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -19980,7 +20817,7 @@ snapshots: y18n: 5.0.8 yargs-parser: 20.2.9 - yargs@17.7.3: + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -19999,6 +20836,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yn@3.1.1: {} yocto-queue@0.1.0: {} @@ -20013,14 +20855,18 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-to-json-schema@3.25.2(zod@4.4.3): + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-json-schema@3.25.1(zod@4.3.6): dependencies: - zod: 4.4.3 + zod: 4.3.6 zod@3.25.76: {} - zod@4.4.3: {} + zod@4.3.6: {} - zone.js@0.16.2: {} + zone.js@0.16.0: {} zx@8.8.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 459f8ca24a2e..896ab3c1d241 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,23 +18,23 @@ packages: - src/youtube-player catalog: - '@angular-devkit/build-angular': 22.1.0-next.4 - '@angular-devkit/core': 22.1.0-next.4 - '@angular-devkit/schematics': 22.1.0-next.4 - '@angular/build': 22.1.0-next.4 - '@angular/cli': 22.1.0-next.4 - '@angular/common': 22.1.0-next.6 - '@angular/compiler-cli': 22.1.0-next.6 - '@angular/compiler': 22.1.0-next.6 - '@angular/core': 22.1.0-next.6 - '@angular/ssr': 22.1.0-next.4 - '@angular/forms': 22.1.0-next.6 - '@angular/localize': 22.1.0-next.6 - '@angular/platform-browser': 22.1.0-next.6 - '@angular/platform-browser-dynamic': 22.1.0-next.6 - '@angular/platform-server': 22.1.0-next.6 - '@angular/router': 22.1.0-next.6 - '@schematics/angular': 22.1.0-next.4 + '@angular-devkit/build-angular': 21.2.0-next.2 + '@angular-devkit/core': 21.2.0-next.2 + '@angular-devkit/schematics': 21.2.0-next.2 + '@angular/build': 21.2.0-next.2 + '@angular/cli': 21.2.0-next.2 + '@angular/common': 21.2.0-next.3 + '@angular/compiler-cli': 21.2.0-next.3 + '@angular/compiler': 21.2.0-next.3 + '@angular/core': 21.2.0-next.3 + '@angular/ssr': 21.2.0-next.2 + '@angular/forms': 21.2.0-next.3 + '@angular/localize': 21.2.0-next.3 + '@angular/platform-browser': 21.2.0-next.3 + '@angular/platform-browser-dynamic': 21.2.0-next.3 + '@angular/platform-server': 21.2.0-next.3 + '@angular/router': 21.2.0-next.3 + '@schematics/angular': 21.2.0-next.2 'rxjs': ^6.6.7 # The minimum age of a release to be considered for dependency installation. @@ -47,54 +47,3 @@ minimumReleaseAgeExclude: - '@ngtools/webpack' - '@schematics/*' - 'ng-packagr' -overrides: - typescript: 6.0.2 - protobufjs: 8.7.1 - '@nginfra/angular-linking>@babel/core': 8.0.1 -packageExtensions: - dgeni-packages: - dependencies: - clonedeep: '*' - grpc-gcp: - dependencies: - protobufjs: 7.4.0 - '@angular-eslint/eslint-plugin-template@19.3.0': - peerDependencies: - '@typescript-eslint/parser': '*' - '@angular-eslint/template-parser': '*' - '@angular/common': - dependencies: - '@nginfra/angular-linking': 1.0.11 - '@angular/forms': - dependencies: - '@nginfra/angular-linking': 1.0.11 - '@angular/platform-browser': - dependencies: - '@nginfra/angular-linking': 1.0.11 - '@angular/router': - dependencies: - '@nginfra/angular-linking': 1.0.11 - '@angular/localize': - dependencies: - '@nginfra/angular-linking': 1.0.11 - '@angular/platform-server': - dependencies: - '@nginfra/angular-linking': 1.0.11 -engineStrict: false -hoist: false -autoInstallPeers: false - -# Avoid prompting for confirmation when pnpm determines node_modules needs to be purged and recreated. -confirmModulesPurge: false - -allowBuilds: - '@firebase/util': false - '@google/genai': false - '@parcel/watcher': false - bufferutil: false - esbuild: false - lmdb: false - msgpackr-extract: false - protobufjs: false - re2: false - utf-8-validate: false diff --git a/renovate.json b/renovate.json index e518a35532bc..f6f2777e00ce 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>angular/dev-infra//renovate-presets/default.json5"], - "baseBranchPatterns": ["main", "22.0.x"], + "baseBranchPatterns": ["main", "21.2.x"], "ignoreDeps": ["stylelint", "selenium-webdriver", "@types/selenium-webdriver", "typescript"], "ignorePaths": ["docs/src/assets/stackblitz/**", "integration/**"] } diff --git a/scripts/approve-api-golden.mts b/scripts/approve-api-golden.mts index 84a6964ca5a4..fd3a417c6fed 100644 --- a/scripts/approve-api-golden.mts +++ b/scripts/approve-api-golden.mts @@ -1,11 +1,11 @@ #!/usr/bin/env node import chalk from 'chalk'; -import {execFileSync} from 'child_process'; import {join} from 'path'; import sh from 'shelljs'; import {guessPackageName} from './util.mjs'; +const bazel = process.env['BAZEL'] || 'pnpm -s bazel'; const targetsToRun = new Set(); if (process.argv.length < 3) { @@ -34,5 +34,5 @@ for (const searchPackageName of process.argv.slice(2)) { } for (const target of targetsToRun) { - execFileSync('pnpm', ['-s', 'bazel', 'run', target], {stdio: 'inherit'}); + sh.exec(`${bazel} run ${target}`); } diff --git a/scripts/circleci/setup-angular-snapshots.js b/scripts/circleci/setup-angular-snapshots.js index 4adb4ce70a32..932d0b0f433f 100644 --- a/scripts/circleci/setup-angular-snapshots.js +++ b/scripts/circleci/setup-angular-snapshots.js @@ -75,11 +75,11 @@ packagesToUpdate.forEach(({name, repoName}) => { // Since the resolutions only cover the version of all nested installs, we also need // to explicitly set the version for the package listed in the project "package.json". - if (packageJson.devDependencies && packageJson.devDependencies[name]) { - packageJson.devDependencies[name] = buildsUrl; - } else { - packageJson.dependencies[name] = buildsUrl; - } + packageJson.dependencies[name] = buildsUrl; + + // In case this dependency was previously a dev dependency, just remove it because we + // re-added it as a normal dependency for simplicity. + delete packageJson.devDependencies[name]; }); // Write changes to the "packageJson", so that we can install the new versions afterwards. diff --git a/scripts/create-legacy-tests-bundle.mjs b/scripts/create-legacy-tests-bundle.mjs index aa4e75ff7897..9754f6d82567 100644 --- a/scripts/create-legacy-tests-bundle.mjs +++ b/scripts/create-legacy-tests-bundle.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import {transformAsync} from '@babel/core'; +import babel from '@babel/core'; import child_process from 'child_process'; import esbuild from 'esbuild'; import fs from 'fs'; @@ -173,7 +173,7 @@ function createLinkerEsbuildPlugin() { build.onLoad({filter: /fesm2022/}, async args => { const filePath = args.path; const content = await fs.promises.readFile(filePath, 'utf8'); - const {code} = await transformAsync(content, { + const {code} = await babel.transformAsync(content, { filename: filePath, compact: false, plugins: [['@angular/compiler-cli/linker/babel', {linkerJitMode: true}]], diff --git a/scripts/create-package-archives.mjs b/scripts/create-package-archives.mjs index 8c7e93861d20..537e0e782a05 100755 --- a/scripts/create-package-archives.mjs +++ b/scripts/create-package-archives.mjs @@ -10,7 +10,6 @@ */ import {join} from 'path'; -import {execFileSync} from 'child_process'; import sh from 'shelljs'; import chalk from 'chalk'; import yargs from 'yargs'; @@ -41,38 +40,16 @@ const builtPackages = sh // If multiple packages should be archived, we also generate a single archive that // contains all packages. This makes it easier to transfer the release packages. if (builtPackages.length > 1) { - console.info('Creating archive with all packages...'); - execFileSync( - 'tar', - [ - '--create', - '--gzip', - '--directory', - releasesDir, - '--file', - `${archivesDir}/all-${suffix}.tgz`, - '.', - ], - {stdio: 'inherit'}, + console.info('Creating archive with all packages..'); + sh.exec( + `tar --create --gzip --directory ${releasesDir} --file ${archivesDir}/all-${suffix}.tgz .`, ); } -// Note that we're using `exec` here, rather than interpolating the arguments into `sh.exec`, -// to avoid a potential command injection through the `suffix` which is user-provided. for (const pkg of builtPackages) { console.info(`Creating archive for package: ${pkg.name}`); - execFileSync( - 'tar', - [ - '--create', - '--gzip', - '--directory', - pkg.path, - '--file', - `${archivesDir}/${pkg.name}-${suffix}.tgz`, - '.', - ], - {stdio: 'inherit'}, + sh.exec( + `tar --create --gzip --directory ${pkg.path} --file ${archivesDir}/${pkg.name}-${suffix}.tgz .`, ); } diff --git a/scripts/docs-deploy/deploy-to-site.mts b/scripts/docs-deploy/deploy-to-site.mts index ba209a3aebb4..ea9e622d67cc 100644 --- a/scripts/docs-deploy/deploy-to-site.mts +++ b/scripts/docs-deploy/deploy-to-site.mts @@ -24,6 +24,9 @@ export interface PreviewDeployment extends Deployment { /** Type describing a Firebase deployment. */ export type DeploymentInfo = ProductionDeployment | PreviewDeployment; +/** Path to a temporary file for the GCP service key credentials file. */ +const gcpServiceKeyTmpFile = path.join(os.tmpdir(), 'mat-docs-deploy-gcp-key.json'); + /** * Deploys the docs site at the specified directory to Firebase with respect * to the deployment information provided. @@ -39,38 +42,26 @@ export async function deployToSite( const firebase = async (...cmd: string[]) => $`pnpm --dir=${projectPath}/docs firebase --non-interactive ${cmd}`; - const gcpServiceKeyTmpFile = path.join(os.tmpdir(), 'mat-docs-deploy-gcp-key.json'); - // Setup GCP service key for the docs-app deployment. // https://firebase.google.com/docs/admin/setup. await fs.promises.writeFile(gcpServiceKeyTmpFile, firebaseServiceKey); + process.env['GOOGLE_APPLICATION_CREDENTIALS'] = gcpServiceKeyTmpFile; - try { - process.env['GOOGLE_APPLICATION_CREDENTIALS'] = gcpServiceKeyTmpFile; + await firebase('use', info.projectId, '--debug'); + await firebase('target:clear', 'hosting', 'mat-aio'); + await firebase('target:apply', 'hosting', 'mat-aio', info.site.firebaseSiteId); - await firebase('use', info.projectId, '--debug'); - await firebase('target:clear', 'hosting', 'mat-aio'); - await firebase('target:apply', 'hosting', 'mat-aio', info.site.firebaseSiteId); + if (isPreviewDeployment(info)) { + const channelId = info.channelId; + const expires = info.expires; - if (isPreviewDeployment(info)) { - const channelId = info.channelId; - const expires = info.expires; - - await firebase( - 'hosting:channel:deploy', - channelId, - '--only', - 'mat-aio', - '--expires', - expires, - ); - } else { - await firebase('deploy', '--only', 'hosting:mat-aio', '--message', info.description); - } - } finally { - process.env['GOOGLE_APPLICATION_CREDENTIALS'] = undefined; - await fs.promises.rm(gcpServiceKeyTmpFile, {force: true}); + await firebase('hosting:channel:deploy', channelId, '--only', 'mat-aio', '--expires', expires); + } else { + await firebase('deploy', '--only', 'hosting:mat-aio', '--message', info.description); } + + // Remove the temporary service key file (this is an optional step and just for sanity). + await fs.promises.rm(gcpServiceKeyTmpFile, {force: true}); } /** Whether the given deployment info corresponds to a preview deployment. */ diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 8806248f8bca..d1fc3007b38a 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "outDir": "../dist/dev-infra-scripts", - "rootDir": "../", "target": "es2020", "lib": ["es2020"], "module": "Node16", @@ -10,6 +9,7 @@ "noEmit": true, "noPropertyAccessFromIndexSignature": true, "skipLibCheck": true, + "downlevelIteration": true, "esModuleInterop": true } } diff --git a/src/BUILD.bazel b/src/BUILD.bazel index 33bf075e36ce..309cb535db9a 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -1,6 +1,6 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@aspect_rules_js//js:defs.bzl", "js_library") load("@aspect_rules_ts//ts:defs.bzl", "ts_config") -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS") load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS") load("//tools:defaults.bzl", "ts_project") diff --git a/src/aria/BUILD.bazel b/src/aria/BUILD.bazel index f634f4d0e925..fdb377d9d0e3 100644 --- a/src/aria/BUILD.bazel +++ b/src/aria/BUILD.bazel @@ -1,7 +1,7 @@ -load("@bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") load("@npm//:defs.bzl", "npm_link_all_packages") load("//src/aria:config.bzl", "ARIA_TARGETS") -load("//tools:defaults.bzl", "ng_package", "ts_project") +load("//tools:defaults.bzl", "ng_examples_db", "ng_package", "ts_project") package(default_visibility = ["//visibility:public"]) @@ -16,11 +16,25 @@ ts_project( deps = ["//:node_modules/@angular/core"], ) +ng_examples_db( + name = "aria_example_database", + srcs = glob( + include = [ + "examples/**/*.md", + ], + # TODO: Remove this once examples are created. It should never be empty at that point. + allow_empty = True, + ), + out = "resources/code-examples.db", + path = "src/aria/examples", +) + ng_package( name = "npm_package", package_name = "@angular/aria", srcs = [ "package.json", + "resources/code-examples.db", ], nested_packages = select({ "//:snapshot_adev_assets": [":adev_assets"], diff --git a/src/aria/accordion/BUILD.bazel b/src/aria/accordion/BUILD.bazel index 033b5bf70188..51ffb8340196 100644 --- a/src/aria/accordion/BUILD.bazel +++ b/src/aria/accordion/BUILD.bazel @@ -11,10 +11,8 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//src/aria/private", - "//src/aria/private/utils", "//src/cdk/a11y", "//src/cdk/bidi", - "//src/cdk/testing", ], ) @@ -28,10 +26,7 @@ ng_project( ":accordion", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//src/aria/private/testing", - "//src/cdk/testing", "//src/cdk/testing/private", - "//src/cdk/testing/testbed", ], ) diff --git a/src/aria/accordion/accordion-content.ts b/src/aria/accordion/accordion-content.ts index af0e6a5eea08..94dac47652a0 100644 --- a/src/aria/accordion/accordion-content.ts +++ b/src/aria/accordion/accordion-content.ts @@ -18,13 +18,14 @@ import {DeferredContent} from '../private'; * by only creating the content when the panel is first expanded. * * ```html - *
+ *
* *

This is the content that will be displayed inside the panel.

*
*
* ``` * + * @developerPreview 21.0 * @see [Accordion](guide/aria/accordion) */ @Directive({ diff --git a/src/aria/accordion/accordion-group.ts b/src/aria/accordion/accordion-group.ts index 2d2657e46d71..4e12b0368347 100644 --- a/src/aria/accordion/accordion-group.ts +++ b/src/aria/accordion/accordion-group.ts @@ -8,20 +8,20 @@ import { Directive, + input, ElementRef, - booleanAttribute, - computed, inject, - input, - signal, - afterNextRender, + contentChildren, afterRenderEffect, - OnDestroy, + signal, + booleanAttribute, + computed, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; -import {AccordionGroupPattern, SortedCollection, reportViolations} from '../private'; -import {ACCORDION_GROUP} from './accordion-tokens'; +import {AccordionGroupPattern, AccordionTriggerPattern} from '../private'; import {AccordionTrigger} from './accordion-trigger'; +import {AccordionPanel} from './accordion-panel'; +import {ACCORDION_GROUP} from './accordion-tokens'; /** * A container for a group of accordion items. It manages the overall state and @@ -32,12 +32,12 @@ import {AccordionTrigger} from './accordion-trigger'; * It supports both single and multiple expansion modes. * * ```html - *
+ *
*
*

- * + * *

- *
+ *
* *

Content for Item 1.

*
@@ -45,9 +45,9 @@ import {AccordionTrigger} from './accordion-trigger'; *
*
*

- * + * *

- *
+ *
* *

Content for Item 2.

*
@@ -56,6 +56,7 @@ import {AccordionTrigger} from './accordion-trigger'; *
* ``` * + * @developerPreview 21.0 * @see [Accordion](guide/aria/accordion) */ @Directive({ @@ -63,25 +64,26 @@ import {AccordionTrigger} from './accordion-trigger'; exportAs: 'ngAccordionGroup', host: { '(keydown)': '_pattern.onKeydown($event)', - '(click)': '_pattern.onClick($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', '(focusin)': '_pattern.onFocus($event)', }, providers: [{provide: ACCORDION_GROUP, useExisting: AccordionGroup}], }) -export class AccordionGroup implements OnDestroy { +export class AccordionGroup { /** A reference to the group element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the group element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of AccordionTriggers. */ - readonly _collection = new SortedCollection(); + /** The AccordionTriggers nested inside this group. */ + private readonly _triggers = contentChildren(AccordionTrigger, {descendants: true}); - /** The corresponding patterns for the accordion triggers. */ - private readonly _triggerPatterns = computed(() => { - return this._collection.orderedItems().map(t => t._pattern); - }); + /** The AccordionTrigger patterns nested inside this group. */ + private readonly _triggerPatterns = computed(() => this._triggers().map(t => t._pattern)); + + /** The AccordionPanels nested inside this group. */ + private readonly _panels = contentChildren(AccordionPanel, {descendants: true}); /** The text direction (ltr or rtl). */ readonly textDirection = inject(Directionality).valueSignal; @@ -104,38 +106,53 @@ export class AccordionGroup implements OnDestroy { /** The UI pattern instance for this accordion group. */ readonly _pattern: AccordionGroupPattern = new AccordionGroupPattern({ ...this, - element: () => this.element, activeItem: signal(undefined), items: this._triggerPatterns, + // TODO(ok7sai): Investigate whether an accordion should support horizontal mode. orientation: () => 'vertical', + getItem: e => this._getItem(e), + element: () => this.element, }); constructor() { - afterNextRender(() => { - this._collection.startObserving(this.element); + // Effect to link triggers with their corresponding panels and update the group's items. + afterRenderEffect(() => { + const triggers = this._triggers(); + const panels = this._panels(); + + for (const trigger of triggers) { + const panel = panels.find(p => p.panelId() === trigger.panelId()); + trigger._accordionPanelPattern.set(panel?._pattern); + if (panel) { + panel._accordionTriggerPattern.set(trigger._pattern); + } + } }); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); - }, - }); - } - } - - ngOnDestroy() { - this._collection.stopObserving(); } /** Expands all accordion panels if multi-expandable. */ expandAll() { - this._pattern.expandAll(); + this._pattern.expansionBehavior.openAll(); } /** Collapses all accordion panels. */ collapseAll() { - this._pattern.collapseAll(); + this._pattern.expansionBehavior.closeAll(); + } + + /** Gets the trigger pattern for a given element. */ + private _getItem(element: Element | null | undefined): AccordionTriggerPattern | undefined { + let target = element; + + while (target) { + const pattern = this._triggerPatterns().find(t => t.element() === target); + if (pattern) { + return pattern; + } + + target = target.parentElement?.closest('[ngAccordionTrigger]'); + } + + return undefined; } } diff --git a/src/aria/accordion/accordion-panel.ts b/src/aria/accordion/accordion-panel.ts index 89d30917de64..e7d0749ef5ca 100644 --- a/src/aria/accordion/accordion-panel.ts +++ b/src/aria/accordion/accordion-panel.ts @@ -8,35 +8,35 @@ import { Directive, - ElementRef, + input, + inject, afterRenderEffect, + signal, computed, - contentChild, - inject, - input, + WritableSignal, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {DeferredContentAware, AccordionTriggerPattern, reportViolations} from '../private'; -import {AccordionContent} from './accordion-content'; +import {DeferredContentAware, AccordionPanelPattern, AccordionTriggerPattern} from '../private'; /** * The content panel of an accordion item that is conditionally visible. * - * This directive is a container for the content that is shown or hidden. It should - * expose a template reference that will be used by the corresponding `ngAccordionTrigger`. + * This directive is a container for the content that is shown or hidden. It requires + * a `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`. * The content within the panel should be provided using an `ng-template` with the * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger * is expanded. It applies `role="region"` for accessibility and uses the `inert` attribute to hide * its content from assistive technologies when not visible. * * ```html - *
+ *
* *

This content is lazily rendered and will be shown when the panel is expanded.

*
*
* ``` * + * @developerPreview 21.0 * @see [Accordion](guide/aria/accordion) */ @Directive({ @@ -50,75 +50,54 @@ import {AccordionContent} from './accordion-content'; ], host: { 'role': 'region', - '[attr.id]': 'id()', - '[attr.aria-labelledby]': '_pattern?.id()', + '[attr.id]': '_pattern.id()', + '[attr.aria-labelledby]': '_pattern.accordionTrigger()?.id()', '[attr.inert]': '!visible() ? true : null', }, }) export class AccordionPanel { - /** A reference to the trigger element. */ - private readonly _elementRef = inject(ElementRef); - - /** A reference to the trigger element. */ - readonly element = this._elementRef.nativeElement as HTMLElement; - /** The DeferredContentAware host directive. */ private readonly _deferredContentAware = inject(DeferredContentAware); - private readonly _accordionContent = contentChild(AccordionContent); - /** A global unique identifier for the panel. */ readonly id = input(inject(_IdGenerator).getId('ng-accordion-panel-', true)); + /** A local unique identifier for the panel, used to match with its trigger's `panelId`. */ + readonly panelId = input.required(); + /** Whether the accordion panel is visible. True if the associated trigger is expanded. */ - readonly visible = computed(() => this._pattern?.expanded() === true); + readonly visible = computed(() => !this._pattern.hidden()); + + /** The parent accordion trigger pattern that controls this panel. This is set by AccordionGroup. */ + readonly _accordionTriggerPattern: WritableSignal = + signal(undefined); - /** - * The pattern for the accordion trigger that controls this panel. - * This is set by the trigger when it initializes. - * There is no need for a panel pattern, as the trigger has all the necessary logic. - */ - _pattern?: AccordionTriggerPattern; + /** The UI pattern instance for this panel. */ + readonly _pattern: AccordionPanelPattern = new AccordionPanelPattern({ + id: this.id, + panelId: this.panelId, + accordionTrigger: () => this._accordionTriggerPattern(), + }); constructor() { // Connect the panel's hidden state to the DeferredContentAware's visibility. - afterRenderEffect({ - write: () => { - this._deferredContentAware.contentVisible.set(this.visible()); - }, + afterRenderEffect(() => { + this._deferredContentAware.contentVisible.set(this.visible()); }); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - - if (!this._accordionContent()) { - violations.push('ngAccordionPanel must have an ngAccordionContent to render.'); - } - if (!this._pattern) { - violations.push('ngAccordionPanel must have an ngAccordionTrigger to control it.'); - } - - reportViolations(violations, this.element); - }, - }); - } } /** Expands this item. */ expand() { - this._pattern?.open(); + this._accordionTriggerPattern()?.open(); } /** Collapses this item. */ collapse() { - this._pattern?.close(); + this._accordionTriggerPattern()?.close(); } /** Toggles the expansion state of this item. */ toggle() { - this._pattern?.toggle(); + this._accordionTriggerPattern()?.toggle(); } } diff --git a/src/aria/accordion/accordion-trigger.ts b/src/aria/accordion/accordion-trigger.ts index 109b5f8b8c64..a2e8fcb5527c 100644 --- a/src/aria/accordion/accordion-trigger.ts +++ b/src/aria/accordion/accordion-trigger.ts @@ -8,36 +8,35 @@ import { Directive, + input, ElementRef, - OnDestroy, - OnInit, - booleanAttribute, - computed, inject, - input, + signal, model, - afterRenderEffect, + booleanAttribute, + computed, + WritableSignal, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {AccordionTriggerPattern, reportViolations} from '../private'; +import {AccordionPanelPattern, AccordionTriggerPattern} from '../private'; import {ACCORDION_GROUP} from './accordion-tokens'; -import {AccordionPanel} from './accordion-panel'; /** * The trigger that toggles the visibility of its associated `ngAccordionPanel`. * - * This directive requires the `panel` input be set to the template reference of the `ngAccordionPanel` - * it controls. When clicked, it will expand or collapse the panel. It also handles keyboard + * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it + * controls. When clicked, it will expand or collapse the panel. It also handles keyboard * interactions for navigation within the `ngAccordionGroup`. It applies `role="button"` and manages * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility. * The `disabled` input can be used to disable the trigger. * * ```html - * * ``` * + * @developerPreview 21.0 * @see [Accordion](guide/aria/accordion) */ @Directive({ @@ -46,7 +45,7 @@ import {AccordionPanel} from './accordion-panel'; host: { '[attr.data-active]': 'active()', 'role': 'button', - '[id]': 'id()', + '[id]': '_pattern.id()', '[attr.aria-expanded]': 'expanded()', '[attr.aria-controls]': '_pattern.controls()', '[attr.aria-disabled]': '_pattern.disabled()', @@ -54,7 +53,7 @@ import {AccordionPanel} from './accordion-panel'; '[attr.tabindex]': '_pattern.tabIndex()', }, }) -export class AccordionTrigger implements OnInit, OnDestroy { +export class AccordionTrigger { /** A reference to the trigger element. */ private readonly _elementRef = inject(ElementRef); @@ -64,14 +63,11 @@ export class AccordionTrigger implements OnInit, OnDestroy { /** The parent AccordionGroup. */ private readonly _accordionGroup = inject(ACCORDION_GROUP); - /** The associated AccordionPanel. */ - readonly panel = input.required(); - - /** The unique identifier for the trigger. */ + /** A unique identifier for the widget. */ readonly id = input(inject(_IdGenerator).getId('ng-accordion-trigger-', true)); - /** The unique identifier for the corresponding trigger panel. */ - readonly panelId = computed(() => this.panel().id()); + /** A local unique identifier for the trigger, used to match with its panel's `panelId`. */ + readonly panelId = input.required(); /** Whether the trigger is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); @@ -82,56 +78,17 @@ export class AccordionTrigger implements OnInit, OnDestroy { /** Whether the trigger is active. */ readonly active = computed(() => this._pattern.active()); - /** The UI pattern instance for this trigger. */ - _pattern!: AccordionTriggerPattern; - - constructor() { - // Automatically prevent form submission. - if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { - this.element.setAttribute('type', 'button'); - } - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; + /** The accordion panel pattern controlled by this trigger. This is set by AccordionGroup. */ + readonly _accordionPanelPattern: WritableSignal = + signal(undefined); - if (this.panel() && this.panel().element.contains(this.element)) { - violations.push( - 'ngAccordionTrigger must not be nested inside its controlled ngAccordionPanel, otherwise it will become unreachable when collapsed.', - ); - } - if (this.panel() && (this.panel() as any)._pattern !== this._pattern) { - violations.push( - 'ngAccordionPanel is already controlled by another ngAccordionTrigger.', - ); - } - - reportViolations(violations, this.element); - }, - }); - } - } - - ngOnInit() { - this._pattern = new AccordionTriggerPattern({ - ...this, - element: () => this.element, - accordionGroup: () => this._accordionGroup._pattern, - accordionPanelId: this.panelId, - }); - - this.panel()._pattern = this._pattern; - - this._accordionGroup._collection.register(this); - } - - ngOnDestroy() { - this.panel()._pattern = undefined; - - this._accordionGroup._collection.unregister(this); - } + /** The UI pattern instance for this trigger. */ + readonly _pattern: AccordionTriggerPattern = new AccordionTriggerPattern({ + ...this, + accordionGroup: computed(() => this._accordionGroup._pattern), + accordionPanel: this._accordionPanelPattern, + element: () => this.element, + }); /** Expands this item. */ expand() { diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index 12d0515b8ad9..ae8b932cedef 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -1,572 +1,375 @@ -import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; +import {Component, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; import {AccordionPanel} from './accordion-panel'; import {AccordionTrigger} from './accordion-trigger'; import {AccordionContent} from './accordion-content'; import {AccordionGroup} from './accordion-group'; describe('AccordionGroup', () => { - let fixture: ComponentFixture; - let testComponent: AccordionGroupWithLoop; - let groupElement: HTMLElement; - + let fixture: ComponentFixture; + let triggerDebugElements: DebugElement[]; + let panelDebugElements: DebugElement[]; let triggerElements: HTMLElement[]; let panelElements: HTMLElement[]; - const click = async (target: HTMLElement) => { - target.dispatchEvent(new PointerEvent('click', {bubbles: true})); - await fixture.whenStable(); - }; - - const keydown = async (key: string, target = groupElement) => { + const keydown = (target: HTMLElement, key: string) => { target.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const spaceKey = async (target: HTMLElement) => await keydown(' ', target); - const enterKey = async (target: HTMLElement) => await keydown('Enter', target); - const downArrowKey = async () => await keydown('ArrowDown'); - const upArrowKey = async () => await keydown('ArrowUp'); - const homeKey = async () => await keydown('Home'); - const endKey = async () => await keydown('End'); - - async function setupAccordionGroup() { - testComponent = fixture.componentInstance as AccordionGroupWithLoop; - groupElement = fixture.nativeElement.querySelector('[ngAccordionGroup]') as HTMLElement; + const click = (target: HTMLElement) => { + target.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true})); + fixture.detectChanges(); + }; - await setupTriggerAndPanels(); + const spaceKey = (target: HTMLElement) => keydown(target, ' '); + const enterKey = (target: HTMLElement) => keydown(target, 'Enter'); + const downArrowKey = (target: HTMLElement) => keydown(target, 'ArrowDown'); + const upArrowKey = (target: HTMLElement) => keydown(target, 'ArrowUp'); + const homeKey = (target: HTMLElement) => keydown(target, 'Home'); + const endKey = (target: HTMLElement) => keydown(target, 'End'); + + interface SetupOptions { + multiExpandable?: boolean; + disabledGroup?: boolean; + expandedItemValues?: string[]; + disabledItemValues?: string[]; + softDisabled?: boolean; + wrap?: boolean; } - async function setupTriggerAndPanels() { - await fixture.whenStable(); + function configureAccordionComponent(opts: SetupOptions = {}) { + const testComponent = fixture.componentInstance as AccordionGroupExample; + + if (opts.multiExpandable !== undefined) { + testComponent.multiExpandable.set(opts.multiExpandable); + } + if (opts.disabledGroup !== undefined) { + testComponent.disabledGroup.set(opts.disabledGroup); + } + if (opts.softDisabled !== undefined) { + testComponent.softDisabled.set(opts.softDisabled); + } + if (opts.wrap !== undefined) { + testComponent.wrap.set(opts.wrap); + } + if (opts.disabledItemValues !== undefined) { + opts.disabledItemValues.forEach(value => testComponent.disableItem(value, true)); + } + if (opts.expandedItemValues !== undefined) { + opts.expandedItemValues.forEach(value => testComponent.expandItem(value, true)); + } + + fixture.detectChanges(); + defineTestVariables(fixture); + } - const triggerDebugElements = fixture.debugElement.queryAll(By.directive(AccordionTrigger)); - const panelDebugElements = fixture.debugElement.queryAll(By.directive(AccordionPanel)); + function defineTestVariables(currentFixture: ComponentFixture) { + triggerDebugElements = currentFixture.debugElement.queryAll(By.directive(AccordionTrigger)); + panelDebugElements = currentFixture.debugElement.queryAll(By.directive(AccordionPanel)); triggerElements = triggerDebugElements.map(el => el.nativeElement); panelElements = panelDebugElements.map(el => el.nativeElement); } - const isTriggerActive = (index: number) => - triggerElements[index].getAttribute('data-active') === 'true'; - const isTriggerExpanded = (index: number) => - triggerElements[index].getAttribute('aria-expanded') === 'true'; + function isTriggerActive(target: HTMLElement): boolean { + return target.getAttribute('data-active') === 'true'; + } - const getTriggerAttribute = (index: number, attribute: string) => - triggerElements[index].getAttribute(attribute); + function isTriggerExpanded(target: HTMLElement): boolean { + return target.getAttribute('aria-expanded') === 'true'; + } - const getPanelAttribute = (index: number, attribute: string) => - panelElements[index].getAttribute(attribute); + afterEach(async () => { + await runAccessibilityChecks(fixture.nativeElement); + }); beforeEach(() => { TestBed.configureTestingModule({ providers: [provideFakeDirectionality('ltr'), _IdGenerator], }); - }); - afterEach(async () => { - await runAccessibilityChecks(fixture.nativeElement); + fixture = TestBed.createComponent(AccordionGroupExample); }); - describe('using a loop', () => { - beforeEach(async () => { - fixture = TestBed.createComponent(AccordionGroupWithLoop); - - await setupAccordionGroup(); - }); - - describe('ARIA attributes and roles', () => { - describe('AccordionTrigger', () => { - it('should have role="button"', () => { - expect(getTriggerAttribute(0, 'role')).toBe('button'); - expect(getTriggerAttribute(1, 'role')).toBe('button'); - expect(getTriggerAttribute(2, 'role')).toBe('button'); - }); - - it('should set type="button" by default on buttons', () => { - expect(getTriggerAttribute(0, 'type')).toBe('button'); - expect(getTriggerAttribute(1, 'type')).toBe('button'); - expect(getTriggerAttribute(2, 'type')).toBe('button'); - }); - - it('should have aria-expanded="false" when collapsed', () => { - expect(getTriggerAttribute(0, 'aria-expanded')).toBe('false'); - expect(getTriggerAttribute(1, 'aria-expanded')).toBe('false'); - expect(getTriggerAttribute(2, 'aria-expanded')).toBe('false'); - }); - - it('should have aria-controls pointing to the panel id', () => { - expect(getTriggerAttribute(0, 'aria-controls')).toBe(panelElements[0].id); - expect(getTriggerAttribute(1, 'aria-controls')).toBe(panelElements[1].id); - expect(getTriggerAttribute(2, 'aria-controls')).toBe(panelElements[2].id); - }); - - it('should have aria-disabled="false" when not disabled', () => { - expect(getTriggerAttribute(0, 'aria-disabled')).toBe('false'); - expect(getTriggerAttribute(1, 'aria-disabled')).toBe('false'); - expect(getTriggerAttribute(2, 'aria-disabled')).toBe('false'); - }); - - it('should set aria-disabled="true" if trigger is disabled', async () => { - testComponent.disableItem('item-1', true); - await fixture.whenStable(); - - expect(getTriggerAttribute(0, 'aria-disabled')).toBe('true'); - expect(getTriggerAttribute(1, 'aria-disabled')).toBe('false'); - expect(getTriggerAttribute(2, 'aria-disabled')).toBe('false'); - }); + describe('ARIA attributes and roles', () => { + describe('AccordionTrigger', () => { + beforeEach(() => { + configureAccordionComponent(); }); - describe('AccordionPanel', () => { - it('should have role="region"', () => { - expect(getPanelAttribute(0, 'role')).toBe('region'); - expect(getPanelAttribute(1, 'role')).toBe('region'); - expect(getPanelAttribute(2, 'role')).toBe('region'); - }); - - it('should have aria-labelledby pointing to the trigger id', () => { - expect(getPanelAttribute(0, 'aria-labelledby')).toBe(getTriggerAttribute(0, 'id')); - expect(getPanelAttribute(1, 'aria-labelledby')).toBe(getTriggerAttribute(1, 'id')); - expect(getPanelAttribute(2, 'aria-labelledby')).toBe(getTriggerAttribute(2, 'id')); - }); - - it('should have "inert" attribute when collapsed', () => { - expect(getPanelAttribute(0, 'inert')).toBe('true'); - expect(getPanelAttribute(1, 'inert')).toBe('true'); - expect(getPanelAttribute(2, 'inert')).toBe('true'); - }); + it('should have role="button"', () => { + expect(triggerElements[0].getAttribute('role')).toBe('button'); + expect(triggerElements[1].getAttribute('role')).toBe('button'); + expect(triggerElements[2].getAttribute('role')).toBe('button'); }); - }); - describe('Expansion behavior', () => { - describe('single expansion mode (multiExpandable=false)', () => { - it('should expand panel on trigger click and update expanded panels', async () => { - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeTrue(); - expect(panelElements[0].getAttribute('inert')).toBe(null); - }); - - it('should collapes panel on trigger click and update expanded panels', async () => { - await click(triggerElements[0]); - await click(triggerElements[0]); // Collapse - expect(isTriggerExpanded(0)).toBeFalse(); - expect(panelElements[0].getAttribute('inert')).toBe('true'); - }); - - it('should expand one and collapse others', async () => { - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeTrue(); - - await click(triggerElements[1]); - expect(isTriggerExpanded(0)).toBeFalse(); - expect(panelElements[0].getAttribute('inert')).toBe('true'); - expect(isTriggerExpanded(1)).toBeTrue(); - expect(panelElements[1].getAttribute('inert')).toBe(null); - }); - - it('should allow setting initial value', async () => { - testComponent.expandItem('item-2', true); - await fixture.whenStable(); - - expect(isTriggerExpanded(0)).toBeFalse(); - expect(isTriggerExpanded(1)).toBeTrue(); - expect(isTriggerExpanded(2)).toBeFalse(); - }); + it('should have aria-expanded="false" when collapsed', () => { + configureAccordionComponent(); + expect(triggerElements[0].getAttribute('aria-expanded')).toBe('false'); + expect(triggerElements[1].getAttribute('aria-expanded')).toBe('false'); + expect(triggerElements[2].getAttribute('aria-expanded')).toBe('false'); }); - describe('multiple expansion mode (multiExpandable=true)', () => { - beforeEach(async () => { - testComponent.multiExpandable.set(true); - await fixture.whenStable(); - }); - - it('should expand multiple panels', async () => { - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeTrue(); - - await click(triggerElements[1]); - expect(isTriggerExpanded(0)).toBeTrue(); - expect(isTriggerExpanded(1)).toBeTrue(); - }); - - it('should collapse an item without affecting others', async () => { - await click(triggerElements[0]); - await click(triggerElements[1]); - expect(isTriggerExpanded(0)).toBeTrue(); - expect(isTriggerExpanded(1)).toBeTrue(); - - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); - expect(isTriggerExpanded(1)).toBeTrue(); - }); - - it('should allow setting initial multiple values', async () => { - testComponent.expandItem('item-1', true); - testComponent.expandItem('item-3', true); - await fixture.whenStable(); - - expect(isTriggerExpanded(0)).toBeTrue(); - expect(isTriggerExpanded(1)).toBeFalse(); - expect(isTriggerExpanded(2)).toBeTrue(); - }); + it('should have aria-controls pointing to the panel id', () => { + expect(triggerElements[0].getAttribute('aria-controls')).toBe(panelElements[0].id); + expect(triggerElements[1].getAttribute('aria-controls')).toBe(panelElements[1].id); + expect(triggerElements[2].getAttribute('aria-controls')).toBe(panelElements[2].id); }); - describe('disabled items and group', () => { - it('should not expand a disabled trigger', async () => { - testComponent.disableItem('item-1', true); - await fixture.whenStable(); - - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); - expect(triggerElements[0].getAttribute('aria-disabled')).toBe('true'); - }); - - it('should not expand any trigger if group is disabled', async () => { - testComponent.disabledGroup.set(true); - await fixture.whenStable(); - - await click(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); + it('should have aria-disabled="false" when not disabled', () => { + configureAccordionComponent({disabledItemValues: []}); + expect(triggerElements[0].getAttribute('aria-disabled')).toBe('false'); + expect(triggerElements[1].getAttribute('aria-disabled')).toBe('false'); + expect(triggerElements[2].getAttribute('aria-disabled')).toBe('false'); + }); - await click(triggerElements[1]); - expect(isTriggerExpanded(1)).toBeFalse(); - }); + it('should set aria-disabled="true" if trigger is disabled', () => { + configureAccordionComponent({disabledItemValues: ['item-1']}); + expect(triggerElements[0].getAttribute('aria-disabled')).toBe('true'); + expect(triggerElements[1].getAttribute('aria-disabled')).toBe('false'); + expect(triggerElements[2].getAttribute('aria-disabled')).toBe('false'); }); }); - describe('Keyboard navigation and interaction', () => { - beforeEach(async () => { - // Focus on the first trigger as initial state. - triggerElements[0].focus(); - await fixture.whenStable(); - expect(isTriggerActive(0)).toBeTrue(); + describe('AccordionPanel', () => { + beforeEach(() => { + configureAccordionComponent(); }); - it('should focus next trigger with ArrowDown', async () => { - await downArrowKey(); - - expect(isTriggerActive(0)).toBeFalse(); - expect(isTriggerActive(1)).toBeTrue(); + it('should have role="region"', () => { + expect(panelElements[0].getAttribute('role')).toBe('region'); + expect(panelElements[1].getAttribute('role')).toBe('region'); + expect(panelElements[2].getAttribute('role')).toBe('region'); }); - it('should focus previous trigger with ArrowUp', async () => { - await downArrowKey(); - expect(isTriggerActive(1)).toBeTrue(); - - await upArrowKey(); - expect(isTriggerActive(1)).toBeFalse(); - expect(isTriggerActive(0)).toBeTrue(); + it('should have aria-labelledby pointing to the trigger id', () => { + expect(panelElements[0].getAttribute('aria-labelledby')).toBe(triggerElements[0].id); + expect(panelElements[1].getAttribute('aria-labelledby')).toBe(triggerElements[1].id); + expect(panelElements[2].getAttribute('aria-labelledby')).toBe(triggerElements[2].id); }); - it('should focus first trigger with Home when another item is focused', async () => { - await downArrowKey(); - await downArrowKey(); - - expect(isTriggerActive(2)).toBeTrue(); - - await homeKey(); - expect(isTriggerActive(0)).toBeTrue(); + it('should have "inert" attribute when collapsed', () => { + configureAccordionComponent(); + expect(panelElements[0].hasAttribute('inert')).toBeTrue(); + expect(panelElements[1].hasAttribute('inert')).toBeTrue(); + expect(panelElements[2].hasAttribute('inert')).toBeTrue(); }); + }); + }); - it('should focus last trigger with End', async () => { - await endKey(); - - expect(isTriggerActive(2)).toBeTrue(); + describe('Expansion behavior', () => { + describe('single expansion mode (multiExpandable=false)', () => { + beforeEach(() => { + configureAccordionComponent({multiExpandable: false}); }); - it('should toggle expansion of focused trigger with Enter', async () => { - expect(isTriggerExpanded(0)).toBeFalse(); - - await enterKey(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeTrue(); - - await enterKey(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); + it('should expand panel on trigger click and update expanded panels', () => { + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + expect(panelElements[0].hasAttribute('inert')).toBeFalse(); }); - it('should toggle expansion of focused trigger with Space', async () => { - expect(isTriggerExpanded(0)).toBeFalse(); - - await spaceKey(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeTrue(); - - await spaceKey(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); + it('should collapes panel on trigger click and update expanded panels', () => { + click(triggerElements[0]); + click(triggerElements[0]); // Collapse + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + expect(panelElements[0].hasAttribute('inert')).toBeTrue(); }); - it('should update collection order when items are shuffled', async () => { - // Verify initial DOM order - expect(triggerElements.length).toBe(3); - expect(triggerElements[0].textContent?.trim()).toBe('Item 1 Header'); - expect(triggerElements[2].textContent?.trim()).toBe('Item 3 Header'); - - // Shuffle (reverse) data - const items = testComponent.items().reverse(); - testComponent.items.set([...items]); - await fixture.whenStable(); - await waitForMicrotasks(); + it('should expand one and collapse others', () => { + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); - // Re-query elements to check new DOM order - await setupTriggerAndPanels(); - - expect(triggerElements.length).toBe(3); - expect(triggerElements[0].textContent?.trim()).toBe('Item 3 Header'); - expect(triggerElements[2].textContent?.trim()).toBe('Item 1 Header'); + click(triggerElements[1]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + expect(panelElements[0].hasAttribute('inert')).toBeTrue(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); + expect(panelElements[1].hasAttribute('inert')).toBeFalse(); }); - describe('wrap behavior', () => { - it('should wrap to first on ArrowDown from last if wrap=true', async () => { - testComponent.wrap.set(true); - await fixture.whenStable(); - - await endKey(); - expect(isTriggerActive(2)).toBeTrue(); - - await downArrowKey(); - expect(isTriggerActive(0)).toBeTrue(); - }); - - it('should not wrap on ArrowDown from last if wrap=false', async () => { - testComponent.wrap.set(false); - await fixture.whenStable(); - - await endKey(); - expect(isTriggerActive(2)).toBeTrue(); - - await downArrowKey(); - expect(isTriggerActive(2)).toBeTrue(); - }); - - it('should wrap to last on ArrowUp from first if wrap=true', async () => { - testComponent.wrap.set(true); - await fixture.whenStable(); - - expect(isTriggerActive(0)).toBeTrue(); - - await upArrowKey(); - expect(isTriggerActive(2)).toBeTrue(); - }); - - it('should not wrap on ArrowUp from first if wrap=false', async () => { - testComponent.wrap.set(false); - await fixture.whenStable(); - - expect(isTriggerActive(0)).toBeTrue(); - - await upArrowKey(); - expect(isTriggerActive(0)).toBeTrue(); - }); + it('should allow setting initial value', () => { + configureAccordionComponent({expandedItemValues: ['item-2'], multiExpandable: false}); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); + expect(isTriggerExpanded(triggerElements[2])).toBeFalse(); }); + }); - describe('softDisabled behavior', () => { - it('should skip disabled items if softDisabled=false', async () => { - testComponent.softDisabled.set(false); - testComponent.disableItem('item-2'); - await fixture.whenStable(); - - expect(isTriggerActive(0)).toBeTrue(); + describe('multiple expansion mode (multiExpandable=true)', () => { + beforeEach(() => { + configureAccordionComponent({multiExpandable: true}); + }); - await downArrowKey(); - expect(isTriggerActive(2)).toBeTrue(); - }); + it('should expand multiple panels', () => { + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); - it('should focus disabled items if softDisabled=true', async () => { - testComponent.softDisabled.set(true); - testComponent.disableItem('item-2'); + click(triggerElements[1]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); + }); - expect(isTriggerActive(0)).toBeTrue(); + it('should collapse an item without affecting others', () => { + click(triggerElements[0]); + click(triggerElements[1]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); - await downArrowKey(); - expect(isTriggerActive(1)).toBeTrue(); + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); + }); - await enterKey(triggerElements[1]); - expect(isTriggerExpanded(1)).toBeFalse(); + it('should allow setting initial multiple values', () => { + configureAccordionComponent({ + expandedItemValues: ['item-1', 'item-3'], + multiExpandable: true, }); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + expect(isTriggerExpanded(triggerElements[1])).toBeFalse(); + expect(isTriggerExpanded(triggerElements[2])).toBeTrue(); }); + }); - it('should not allow keyboard navigation if group is disabled', async () => { - testComponent.disabledGroup.set(true); - await fixture.whenStable(); - - await downArrowKey(); - expect(isTriggerActive(1)).toBeFalse(); + describe('disabled items and group', () => { + it('should not expand a disabled trigger', () => { + configureAccordionComponent({disabledItemValues: ['item-1']}); + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + expect(triggerElements[0].getAttribute('aria-disabled')).toBe('true'); }); - it('should not allow expansion if group is disabled', async () => { - testComponent.disabledGroup.set(true); - await fixture.whenStable(); - - await enterKey(triggerElements[0]); - expect(isTriggerExpanded(0)).toBeFalse(); + it('should not expand any trigger if group is disabled', () => { + configureAccordionComponent({disabledGroup: true}); + click(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + click(triggerElements[1]); + expect(isTriggerExpanded(triggerElements[1])).toBeFalse(); }); }); }); - describe('using an if', () => { - let testComponent: AccordionGroupWithIfs; + describe('Keyboard navigation and interaction', () => { + beforeEach(() => { + configureAccordionComponent({}); - beforeEach(async () => { - fixture = TestBed.createComponent(AccordionGroupWithIfs); - testComponent = fixture.componentInstance as AccordionGroupWithIfs; - groupElement = fixture.nativeElement.querySelector('[ngAccordionGroup]') as HTMLElement; + // Focus on the first trigger as initial state. + triggerElements[0].focus(); + fixture.detectChanges(); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + }); - await setupTriggerAndPanels(); + it('should focus next trigger with ArrowDown', () => { + downArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[0])).toBeFalse(); + expect(isTriggerActive(triggerElements[1])).toBeTrue(); }); - describe('Keyboard navigation and interaction', () => { - beforeEach(async () => { - // Focus on the first trigger as initial state. - triggerElements[0].focus(); - await fixture.whenStable(); - expect(isTriggerActive(0)).toBeTrue(); - }); + it('should focus previous trigger with ArrowUp', () => { + downArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[1])).toBeTrue(); + upArrowKey(triggerElements[1]); + expect(isTriggerActive(triggerElements[1])).toBeFalse(); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + }); - it('should change focus between first and last triggers when second removed', async () => { - testComponent.includeSecond.set(false); - await fixture.whenStable(); + it('should focus first trigger with Home when another item is focused', () => { + downArrowKey(triggerElements[0]); + downArrowKey(triggerElements[1]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); + homeKey(triggerElements[2]); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + }); - await downArrowKey(); - expect(isTriggerActive(0)).toBeFalse(); - expect(isTriggerActive(2)).toBeTrue(); + it('should focus last trigger with End', () => { + endKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); + }); - await upArrowKey(); - expect(isTriggerActive(2)).toBeFalse(); - expect(isTriggerActive(0)).toBeTrue(); - }); + it('should toggle expansion of focused trigger with Enter', () => { + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + enterKey(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + enterKey(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + }); - it('should focus second trigger with Home when first is removed', async () => { - triggerElements[2].focus(); - testComponent.includeFirst.set(false); - await fixture.whenStable(); + it('should toggle expansion of focused trigger with Space', () => { + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + spaceKey(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + spaceKey(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); + }); - await homeKey(); - expect(isTriggerActive(0)).toBeTrue(); + describe('wrap behavior', () => { + it('should wrap to first on ArrowDown from last if wrap=true', () => { + configureAccordionComponent({wrap: true}); + endKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); + downArrowKey(triggerElements[2]); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); }); - it('should focus second trigger with End when last is removed', async () => { - triggerElements[0].focus(); - testComponent.includeThird.set(false); - await fixture.whenStable(); - - await endKey(); - expect(isTriggerActive(1)).toBeTrue(); + it('should not wrap on ArrowDown from last if wrap=false', () => { + configureAccordionComponent({wrap: false}); + endKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); + downArrowKey(triggerElements[2]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); }); - it('should iterate focus through all 3 in order when replaced ', async () => { - testComponent.includeFirst.set(false); - testComponent.includeSecond.set(false); - testComponent.includeThird.set(false); - await fixture.whenStable(); - - testComponent.includeThird.set(true); - testComponent.includeFirst.set(true); - testComponent.includeSecond.set(true); - await setupTriggerAndPanels(); - - triggerElements[0].focus(); - await fixture.whenStable(); - - expect(isTriggerActive(0)).toBeTrue(); - - await downArrowKey(); - expect(isTriggerActive(0)).toBeFalse(); - expect(isTriggerActive(1)).toBeTrue(); - - await downArrowKey(); - expect(isTriggerActive(1)).toBeFalse(); - expect(isTriggerActive(2)).toBeTrue(); - - await downArrowKey(); - expect(isTriggerActive(2)).toBeFalse(); - expect(isTriggerActive(0)).toBeTrue(); + it('should wrap to last on ArrowUp from first if wrap=true', () => { + configureAccordionComponent({wrap: true}); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + upArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); }); - }); - }); - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionGroupWithLoop], - providers: [provideFakeDirectionality('ltr'), _IdGenerator], + it('should not wrap on ArrowUp from first if wrap=false', () => { + configureAccordionComponent({wrap: false}); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + upArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[0])).toBeTrue(); }); - fixture = TestBed.createComponent(AccordionGroupWithLoop); - await setupAccordionGroup(); }); - it('should warn when multiple triggers control the same panel', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionWithDuplicateTriggers], - }); - const duplicateFixture = TestBed.createComponent(AccordionWithDuplicateTriggers); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionPanel is already controlled by another ngAccordionTrigger.', - ); - }); + describe('softDisabled behavior', () => { + it('should skip disabled items if softDisabled=false', () => { + configureAccordionComponent({softDisabled: false, disabledItemValues: ['item-2']}); - it('should warn when trigger is nested inside its controlled panel', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionWithNestedTrigger], + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + downArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[2])).toBeTrue(); }); - const nestedFixture = TestBed.createComponent(AccordionWithNestedTrigger); - nestedFixture.detectChanges(); - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionTrigger must not be nested inside its controlled ngAccordionPanel, otherwise it will become unreachable when collapsed.', - ); - }); + it('should focus disabled items if softDisabled=true', () => { + configureAccordionComponent({softDisabled: true, disabledItemValues: ['item-2']}); - it('should warn when ngAccordionPanel is missing ngAccordionContent', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionPanelWithoutContent], + expect(isTriggerActive(triggerElements[0])).toBeTrue(); + downArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[1])).toBeTrue(); + enterKey(triggerElements[1]); + expect(isTriggerExpanded(triggerElements[1])).toBeFalse(); }); - const noContentFixture = TestBed.createComponent(AccordionPanelWithoutContent); - noContentFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionPanel must have an ngAccordionContent to render.', - ); }); - it('should warn when ngAccordionPanel is missing controlling trigger', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionPanelWithoutTrigger], - }); - const noTriggerFixture = TestBed.createComponent(AccordionPanelWithoutTrigger); - noTriggerFixture.detectChanges(); + it('should not allow keyboard navigation if group is disabled', () => { + configureAccordionComponent({disabledGroup: true}); - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionPanel must have an ngAccordionTrigger to control it.', - ); + downArrowKey(triggerElements[0]); + expect(isTriggerActive(triggerElements[1])).toBeFalse(); }); - it('should warn when multiple items are expanded in single-expand mode', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionWithMultipleExpandedItems], - }); - const multipleExpandedFixture = TestBed.createComponent(AccordionWithMultipleExpandedItems); - multipleExpandedFixture.detectChanges(); + it('should not allow expansion if group is disabled', () => { + configureAccordionComponent({disabledGroup: true}); - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionGroup has multiExpandable set to false, but multiple ngAccordionTrigger panels are initially expanded.', - ); + enterKey(triggerElements[0]); + expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); }); }); }); @@ -581,16 +384,16 @@ describe('AccordionGroup', () => { [wrap]="wrap()" > @for (item of items(); track item.panelId) { -
+
{{ item.content }} @@ -601,30 +404,29 @@ describe('AccordionGroup', () => {
`, imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, }) -class AccordionGroupWithLoop { +class AccordionGroupExample { items = signal([ { panelId: 'item-1', header: 'Item 1 Header', content: 'Item 1 Content', - disabled: signal(false), - expanded: signal(false), + disabled: false, + expanded: false, }, { panelId: 'item-2', header: 'Item 2 Header', content: 'Item 2 Content', - disabled: signal(false), - expanded: signal(false), + disabled: false, + expanded: false, }, { panelId: 'item-3', header: 'Item 3 Header', content: 'Item 3 Content', - disabled: signal(false), - expanded: signal(false), + disabled: false, + expanded: false, }, ]); @@ -633,143 +435,15 @@ class AccordionGroupWithLoop { softDisabled = signal(true); wrap = signal(false); - disableItem(itemValue: string, disabled = true) { - this.items() - .find(item => item.panelId === itemValue) - ?.disabled.set(disabled); + disableItem(itemValue: string, disabled: boolean) { + this.items.update(items => + items.map(item => (item.panelId === itemValue ? {...item, disabled} : item)), + ); } - expandItem(itemValue: string, expanded = true) { - this.items() - .find(item => item.panelId === itemValue) - ?.expanded.set(expanded); + expandItem(itemValue: string, expanded: boolean) { + this.items.update(items => + items.map(item => (item.panelId === itemValue ? {...item, expanded} : item)), + ); } } - -@Component({ - template: ` -
- @if (includeFirst()) { -
- -
- - Item 1 Content - -
-
- } - @if (includeSecond()) { -
- -
- - Item 2 Content - -
-
- } - @if (includeThird()) { -
- -
- - Item 3 Content - -
-
- } -
- `, - imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionGroupWithIfs extends AccordionGroupWithLoop { - includeFirst = signal(true); - includeSecond = signal(true); - includeThird = signal(true); -} - -@Component({ - template: ` -
- - -
- Content -
-
- `, - imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionWithDuplicateTriggers {} - -@Component({ - template: ` -
-
- - Content -
-
- `, - imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionWithNestedTrigger {} - -@Component({ - template: ` -
- -
- Content -
-
- `, - imports: [AccordionGroup, AccordionTrigger, AccordionPanel], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionPanelWithoutContent {} - -@Component({ - template: ` -
-
- Content -
-
- `, - imports: [AccordionGroup, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionPanelWithoutTrigger {} - -@Component({ - template: ` -
-
- -
- Content 1 -
-
-
- -
- Content 2 -
-
-
- `, - imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class AccordionWithMultipleExpandedItems {} diff --git a/src/aria/accordion/testing/BUILD.bazel b/src/aria/accordion/testing/BUILD.bazel deleted file mode 100644 index daefc5897c1a..000000000000 --- a/src/aria/accordion/testing/BUILD.bazel +++ /dev/null @@ -1,42 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/aria/accordion", - "//src/cdk/testing", - "//src/cdk/testing/private", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [ - ":unit_tests_lib", - ], -) diff --git a/src/aria/accordion/testing/accordion-harness-filters.ts b/src/aria/accordion/testing/accordion-harness-filters.ts deleted file mode 100644 index 7c736ddb1c51..000000000000 --- a/src/aria/accordion/testing/accordion-harness-filters.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** Filters for locating an `AccordionHarness`. */ -export interface AccordionHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose title text matches the given value. */ - title?: string | RegExp; - /** Only find instances whose expanded state matches the given value. */ - expanded?: boolean; - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; -} - -/** Filters for locating an `AccordionGroupHarness`. */ -export interface AccordionGroupHarnessFilters extends BaseHarnessFilters {} diff --git a/src/aria/accordion/testing/accordion-harness.spec.ts b/src/aria/accordion/testing/accordion-harness.spec.ts deleted file mode 100644 index cd43d4096b21..000000000000 --- a/src/aria/accordion/testing/accordion-harness.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {ComponentHarness} from '@angular/cdk/testing'; -import {AccordionHarness, AccordionGroupHarness} from './accordion-harness'; -import {AccordionGroup, AccordionPanel, AccordionTrigger} from '../index'; - -/** Lightweight test harness to test querying inside the accordion body panel. */ -class TestButtonHarness extends ComponentHarness { - static hostSelector = 'button.test-button'; - - async getText(): Promise { - return (await this.host()).text(); - } -} - -describe('Accordion Harnesses', () => { - let fixture: any; - let loader: any; - - @Component({ - imports: [AccordionGroup, AccordionPanel, AccordionTrigger], - template: ` -
-
- -
- - -
Content 2
- -
- `, - }) - class AccordionHarnessTestComponent {} - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [AccordionHarnessTestComponent], - }); - fixture = TestBed.createComponent(AccordionHarnessTestComponent); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should find accordion group and list all scoped accordions using getAccordions', async () => { - const group = await loader.getHarness(AccordionGroupHarness); - const accordions = await group.getAccordions(); - - expect(accordions.length).toBe(2); - expect(await accordions[0].getTitle()).toBe('Section 1'); - expect(await accordions[1].getTitle()).toBe('Section 2'); - }); - - it('should find all individual accordions via standard root loader', async () => { - const accordions = await loader.getAllHarnesses(AccordionHarness); - expect(accordions.length).toBe(2); - }); - - it('should filter accordions by title', async () => { - const accordions = await loader.getAllHarnesses(AccordionHarness.with({title: 'Section 1'})); - expect(accordions.length).toBe(1); - expect(await accordions[0].getTitle()).toBe('Section 1'); - }); - - it('should filter accordions by expanded state', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - await accordion.expand(); - - const expandedAccordions = await loader.getAllHarnesses( - AccordionHarness.with({expanded: true}), - ); - expect(expandedAccordions.length).toBe(1); - expect(await expandedAccordions[0].getTitle()).toBe('Section 1'); - }); - - it('should filter accordions by disabled state', async () => { - const disabledAccordions = await loader.getAllHarnesses( - AccordionHarness.with({disabled: true}), - ); - expect(disabledAccordions.length).toBe(1); - expect(await disabledAccordions[0].getTitle()).toBe('Section 2'); - }); - - it('should get the title of the accordion', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - expect(await accordion.getTitle()).toBe('Section 1'); - }); - - it('should correctly report the expanded state of an accordion', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - expect(await accordion.isExpanded()).toBeFalse(); - }); - - it('should correctly report the disabled state of an accordion', async () => { - const activeAccordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - const disabledAccordion = await loader.getHarness(AccordionHarness.with({title: 'Section 2'})); - - expect(await activeAccordion.isDisabled()).toBeFalse(); - expect(await disabledAccordion.isDisabled()).toBeTrue(); - }); - - it('expands a collapsed accordion using the expand method', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - - await accordion.expand(); - - expect(await accordion.isExpanded()).toBeTrue(); - }); - - it('collapses an expanded accordion using the collapse method', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - await accordion.expand(); - - await accordion.collapse(); - - expect(await accordion.isExpanded()).toBeFalse(); - }); - - it('toggles the expanded state of an accordion using the toggle method', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - - await accordion.toggle(); - - expect(await accordion.isExpanded()).toBeTrue(); - }); - - it('should support focusing and blurring accordion triggers', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - await accordion.focus(); - expect(await accordion.isFocused()).toBeTrue(); - - await accordion.blur(); - expect(await accordion.isFocused()).toBeFalse(); - }); - - it('should query components inside the accordion panel using ContentContainerComponentHarness', async () => { - const accordion = await loader.getHarness(AccordionHarness.with({title: 'Section 1'})); - const button = await accordion.getHarness(TestButtonHarness); - expect(await button.getText()).toBe('Inside Content 1'); - }); -}); diff --git a/src/aria/accordion/testing/accordion-harness.ts b/src/aria/accordion/testing/accordion-harness.ts deleted file mode 100644 index d37c40bdac01..000000000000 --- a/src/aria/accordion/testing/accordion-harness.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - ComponentHarness, - ContentContainerComponentHarness, - HarnessPredicate, -} from '@angular/cdk/testing'; -import {AccordionHarnessFilters, AccordionGroupHarnessFilters} from './accordion-harness-filters'; - -/** Selectors for the sections that may contain user content. */ -export enum AccordionSection { - TRIGGER = '[ngAccordionTrigger]', - PANEL = '[ngAccordionPanel]', -} - -/** Harness for interacting with a standard ngAccordion item in tests. */ -export class AccordionHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngAccordionTrigger]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for an accordion - * with specific attributes. - */ - static with(options: AccordionHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(AccordionHarness, options) - .addOption('title', options.title, (harness, title) => - HarnessPredicate.stringMatches(harness.getTitle(), title), - ) - .addOption( - 'expanded', - options.expanded, - async (harness, expanded) => (await harness.isExpanded()) === expanded, - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Overrides the internal loader to automatically resolve queries inside the associated panel. */ - protected override async getRootHarnessLoader() { - const panelId = await (await this.host()).getAttribute('aria-controls'); - const documentRoot = await this.documentRootLocatorFactory().rootHarnessLoader(); - return documentRoot.getChildLoader(`[ngAccordionPanel][id="${panelId}"]`); - } - - /** Whether the accordion is expanded. */ - async isExpanded(): Promise { - return (await (await this.host()).getAttribute('aria-expanded')) === 'true'; - } - - /** Whether the accordion is disabled. */ - async isDisabled(): Promise { - return (await (await this.host()).getAttribute('aria-disabled')) === 'true'; - } - - /** Gets the title text of the accordion. */ - async getTitle(): Promise { - return (await this.host()).text(); - } - - /** Toggles the expanded state of the accordion by clicking on the trigger. */ - async toggle(): Promise { - await (await this.host()).click(); - } - - /** Expands the accordion if collapsed. */ - async expand(): Promise { - if (!(await this.isExpanded())) { - await this.toggle(); - } - } - - /** Collapses the accordion if expanded. */ - async collapse(): Promise { - if (await this.isExpanded()) { - await this.toggle(); - } - } - - /** Focuses the accordion trigger. */ - async focus(): Promise { - await (await this.host()).focus(); - } - - /** Blurs the accordion trigger. */ - async blur(): Promise { - await (await this.host()).blur(); - } - - /** Whether the accordion trigger is focused. */ - async isFocused(): Promise { - return (await this.host()).isFocused(); - } -} - -/** Harness for interacting with an `ngAccordionGroup` in tests. */ -export class AccordionGroupHarness extends ComponentHarness { - static hostSelector = '[ngAccordionGroup]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for an accordion group with specific attributes. - */ - static with(options: AccordionGroupHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(AccordionGroupHarness, options); - } - - /** Gets all accordions within this group. */ - async getAccordions(filters: AccordionHarnessFilters = {}): Promise { - return this.locatorForAll(AccordionHarness.with(filters))(); - } -} diff --git a/src/aria/accordion/testing/index.ts b/src/aria/accordion/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/accordion/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/accordion/testing/public-api.ts b/src/aria/accordion/testing/public-api.ts deleted file mode 100644 index acb15bdb15bd..000000000000 --- a/src/aria/accordion/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './accordion-harness'; -export * from './accordion-harness-filters'; diff --git a/src/aria/combobox/BUILD.bazel b/src/aria/combobox/BUILD.bazel index d39dc069bf79..e0b7cbae2ad0 100644 --- a/src/aria/combobox/BUILD.bazel +++ b/src/aria/combobox/BUILD.bazel @@ -27,7 +27,7 @@ ts_project( "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//src/aria/grid", + "//:node_modules/axe-core", "//src/aria/listbox", "//src/aria/tree", "//src/cdk/testing/private", diff --git a/src/aria/combobox/combobox-dialog.ts b/src/aria/combobox/combobox-dialog.ts new file mode 100644 index 000000000000..903d3087d3c0 --- /dev/null +++ b/src/aria/combobox/combobox-dialog.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {afterRenderEffect, Directive, ElementRef, inject} from '@angular/core'; +import {ComboboxDialogPattern} from '../private'; +import {Combobox} from './combobox'; +import {ComboboxPopup} from './combobox-popup'; + +/** + * Integrates a native `` element with the combobox, allowing for + * a modal or non-modal popup experience. It handles the opening and closing of the dialog + * based on the combobox's expanded state. + * + * ```html + * + * + * + * + * + * ``` + * + * @developerPreview 21.0 + * + * @see [Combobox](guide/aria/combobox) + * @see [Select](guide/aria/select) + * @see [Multiselect](guide/aria/multiselect) + * @see [Autocomplete](guide/aria/autocomplete) + */ +@Directive({ + selector: 'dialog[ngComboboxDialog]', + exportAs: 'ngComboboxDialog', + host: { + '[attr.data-open]': 'combobox._pattern.expanded()', + '(keydown)': '_pattern.onKeydown($event)', + '(click)': '_pattern.onClick($event)', + }, + hostDirectives: [ComboboxPopup], +}) +export class ComboboxDialog { + /** The dialog element. */ + private readonly _elementRef = inject(ElementRef); + + /** A reference to the dialog element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** The combobox that the dialog belongs to. */ + readonly combobox = inject(Combobox); + + /** A reference to the parent combobox popup, if one exists. */ + private readonly _popup = inject>(ComboboxPopup, { + optional: true, + }); + + _pattern: ComboboxDialogPattern; + + constructor() { + this._pattern = new ComboboxDialogPattern({ + id: () => '', + element: () => this._elementRef.nativeElement, + combobox: this.combobox._pattern, + }); + + if (this._popup) { + this._popup._controls.set(this._pattern); + } + + afterRenderEffect(() => { + if (this._elementRef) { + this.combobox._pattern.expanded() + ? this._elementRef.nativeElement.showModal() + : this._elementRef.nativeElement.close(); + } + }); + } + + close() { + this._popup?.combobox?._pattern.close(); + } +} diff --git a/src/aria/combobox/combobox-input.ts b/src/aria/combobox/combobox-input.ts new file mode 100644 index 000000000000..3a79ba895b77 --- /dev/null +++ b/src/aria/combobox/combobox-input.ts @@ -0,0 +1,90 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + afterRenderEffect, + Directive, + ElementRef, + inject, + model, + untracked, + WritableSignal, +} from '@angular/core'; +import {ComboboxDialogPattern} from '../private'; +import {Combobox} from './combobox'; + +/** + * An input that is part of a combobox. It is responsible for displaying the + * current value and handling user input for filtering and selection. + * + * This directive should be applied to an `` element within an `ngCombobox` + * container. It automatically handles keyboard interactions, such as opening the + * popup and navigating through the options. + * + * ```html + * + * ``` + * + * @developerPreview 21.0 + * + * @see [Combobox](guide/aria/combobox) + * @see [Select](guide/aria/select) + * @see [Multiselect](guide/aria/multiselect) + * @see [Autocomplete](guide/aria/autocomplete) + */ +@Directive({ + selector: 'input[ngComboboxInput]', + exportAs: 'ngComboboxInput', + host: { + 'role': 'combobox', + '[value]': 'value()', + '[attr.aria-disabled]': 'combobox._pattern.disabled()', + '[attr.aria-expanded]': 'combobox._pattern.expanded()', + '[attr.aria-activedescendant]': 'combobox._pattern.activeDescendant()', + '[attr.aria-controls]': 'combobox._pattern.popupId()', + '[attr.aria-haspopup]': 'combobox._pattern.hasPopup()', + '[attr.aria-autocomplete]': 'combobox._pattern.autocomplete()', + '[attr.readonly]': 'combobox._pattern.readonly()', + }, +}) +export class ComboboxInput { + /** The element that the combobox is attached to. */ + private readonly _elementRef = inject>(ElementRef); + + /** A reference to the input element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** The combobox that the input belongs to. */ + readonly combobox = inject(Combobox); + + /** The value of the input. */ + value = model(''); + + constructor() { + (this.combobox._pattern.inputs.inputEl as WritableSignal).set( + this._elementRef.nativeElement, + ); + this.combobox._pattern.inputs.inputValue = this.value; + + const controls = this.combobox.popup()?._controls(); + if (controls instanceof ComboboxDialogPattern) { + return; + } + + /** Focuses & selects the first item in the combobox if the user changes the input value. */ + afterRenderEffect(() => { + this.value(); + controls?.items(); + untracked(() => this.combobox._pattern.onFilter()); + }); + } +} diff --git a/src/aria/combobox/combobox-popup-container.ts b/src/aria/combobox/combobox-popup-container.ts new file mode 100644 index 000000000000..d25e3bb90531 --- /dev/null +++ b/src/aria/combobox/combobox-popup-container.ts @@ -0,0 +1,52 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Directive} from '@angular/core'; +import {DeferredContent} from '../private'; + +/** + * A structural directive that marks the `ng-template` to be used as the popup + * for a combobox. This content is conditionally rendered. + * + * The content of the popup can be a `ngListbox`, `ngTree`, or `role="dialog"`, allowing for + * flexible and complex combobox implementations. The consumer is responsible for + * implementing the filtering logic based on the `ngComboboxInput`'s value. + * + * ```html + * + *
+ * + *
+ *
+ * ``` + * + * When using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`. + * + * ```html + * + *
+ * + *
+ *
+ * ``` + * + * @developerPreview 21.0 + * + * @see [Combobox](guide/aria/combobox) + * @see [Select](guide/aria/select) + * @see [Multiselect](guide/aria/multiselect) + * @see [Autocomplete](guide/aria/autocomplete) + */ +@Directive({ + selector: 'ng-template[ngComboboxPopupContainer]', + exportAs: 'ngComboboxPopupContainer', + hostDirectives: [DeferredContent], +}) +export class ComboboxPopupContainer {} diff --git a/src/aria/combobox/combobox-popup.ts b/src/aria/combobox/combobox-popup.ts index b220a5d548fb..3ecc0d227732 100644 --- a/src/aria/combobox/combobox-popup.ts +++ b/src/aria/combobox/combobox-popup.ts @@ -6,74 +6,39 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, Directive, inject, input, OnDestroy, OnInit, signal} from '@angular/core'; -import {DeferredContent, ComboboxPopupPattern} from '@angular/aria/private'; +import {Directive, inject, signal} from '@angular/core'; +import {ComboboxListboxControls, ComboboxTreeControls, ComboboxDialogPattern} from '../private'; import type {Combobox} from './combobox'; -import type {ComboboxWidget} from './combobox-widget'; -import {COMBOBOX_POPUP} from './combobox-tokens'; +import {COMBOBOX} from './combobox-tokens'; /** - * A structural directive that marks the `ng-template` to be used as the popup - * for a combobox. This content is conditionally rendered. + * Identifies an element as a popup for an `ngCombobox`. * - * The content of the popup can be any element with the `ngComboboxWidget` directive. + * This directive acts as a bridge, allowing the `ngCombobox` to discover and interact + * with the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that + * manages the options. It's primarily used as a host directive and is responsible for + * exposing the popup's control pattern to the parent combobox. * - * ```html - * - *
- * - *
- *
- * ``` + * @developerPreview 21.0 + * + * @see [Combobox](guide/aria/combobox) + * @see [Select](guide/aria/select) + * @see [Multiselect](guide/aria/multiselect) + * @see [Autocomplete](guide/aria/autocomplete) */ @Directive({ - selector: 'ng-template[ngComboboxPopup]', + selector: '[ngComboboxPopup]', exportAs: 'ngComboboxPopup', - hostDirectives: [DeferredContent], - providers: [{provide: COMBOBOX_POPUP, useExisting: ComboboxPopup}], }) -export class ComboboxPopup implements OnInit, OnDestroy { - private readonly _deferredContent = inject(DeferredContent); - +export class ComboboxPopup { /** The combobox that the popup belongs to. */ - readonly combobox = input.required(); - - /** The widget contained within the popup. */ - readonly _widget = signal(undefined); - - /** The element that serves as the control target for the popup. */ - readonly controlTarget = computed(() => this._widget()?.element); - - /** The ID of the popup. */ - readonly popupId = computed(() => this._widget()?.popupId()); - - /** The ID of the active descendant in the popup. */ - readonly activeDescendant = computed(() => this._widget()?.activeDescendant()); - - /** The type of the popup (e.g., listbox, tree, grid, dialog). */ - readonly popupType = input<'listbox' | 'tree' | 'grid' | 'dialog'>('listbox'); - - /** The popup pattern. */ - readonly _pattern = new ComboboxPopupPattern({ - ...this, - }); - - ngOnInit() { - this.combobox()._registerPopup(this); - this._deferredContent.deferredContentAware.set(this.combobox()); - } - - ngOnDestroy() { - this.combobox()._unregisterPopup(); - } - - /** Registers a widget with the popup. */ - _registerWidget(widget: ComboboxWidget) { - this._widget.set(widget); - } - - /** Unregisters the widget from the popup. */ - _unregisterWidget() { - this._widget.set(undefined); - } + readonly combobox = inject>(COMBOBOX, {optional: true}); + + /** The popup controls exposed to the combobox. */ + readonly _controls = signal< + | ComboboxListboxControls + | ComboboxTreeControls + | ComboboxDialogPattern + | undefined + >(undefined); } diff --git a/src/aria/combobox/combobox-tokens.ts b/src/aria/combobox/combobox-tokens.ts index 11ab06c43b40..bcc7d9d1c4d8 100644 --- a/src/aria/combobox/combobox-tokens.ts +++ b/src/aria/combobox/combobox-tokens.ts @@ -7,7 +7,7 @@ */ import {InjectionToken} from '@angular/core'; -import type {ComboboxPopup} from './combobox-popup'; +import type {Combobox} from './combobox'; -/** Token used to expose the combobox popup. */ -export const COMBOBOX_POPUP = new InjectionToken('COMBOBOX_POPUP'); +/** Token used to provide the combobox to child components. */ +export const COMBOBOX = new InjectionToken>('COMBOBOX'); diff --git a/src/aria/combobox/combobox-widget.ts b/src/aria/combobox/combobox-widget.ts deleted file mode 100644 index 3f4636e68ab4..000000000000 --- a/src/aria/combobox/combobox-widget.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Directive, ElementRef, inject, input, OnDestroy, OnInit, signal} from '@angular/core'; -import {COMBOBOX_POPUP} from './combobox-tokens'; - -/** - * Identifies an element as a widget within a combobox popup. - * - * This directive should be applied to the element that contains the options or content - * of the popup. It handles the communication of ID and active descendant information - * to the combobox. - */ -@Directive({ - selector: '[ngComboboxWidget]', - exportAs: 'ngComboboxWidget', - host: { - '(focusin)': 'onFocusin()', - '(focusout)': 'onFocusout($event)', - }, -}) -export class ComboboxWidget implements OnInit, OnDestroy { - /** The element that the popup widget is attached to. */ - private readonly _elementRef = inject>(ElementRef); - private readonly _popup = inject(COMBOBOX_POPUP); - - /** A reference to the popup widget element. */ - readonly element = this._elementRef.nativeElement; - - /** The ID of the popup widget. */ - readonly popupId = signal(undefined); - - /** The ID of the active descendant in the widget. */ - readonly activeDescendant = input(undefined); - - private _observer: MutationObserver | undefined; - - constructor() { - const el = this.element; - this._observer = new MutationObserver(mutations => { - for (const mutation of mutations) { - if (mutation.attributeName === 'id') { - this.popupId.set(el.id); - } - } - }); - - this._observer.observe(el, { - attributes: true, - attributeFilter: ['id'], - }); - } - - ngOnInit() { - this.popupId.set(this.element.id); - this._popup._registerWidget(this); - } - - ngOnDestroy(): void { - this._observer?.disconnect(); - this._popup._unregisterWidget(); - } - - /** Handles focus in events for the widget. */ - onFocusin() { - this._popup._pattern.onFocusin(); - } - - /** Handles focus out events for the widget. */ - onFocusout(event: FocusEvent) { - this._popup._pattern.onFocusout(event); - } -} diff --git a/src/aria/combobox/combobox.spec.ts b/src/aria/combobox/combobox.spec.ts index a3ee5b66d957..fd4dc585f5b8 100644 --- a/src/aria/combobox/combobox.spec.ts +++ b/src/aria/combobox/combobox.spec.ts @@ -1,31 +1,19 @@ -import { - Component, - computed, - DebugElement, - signal, - untracked, - viewChild, - afterRenderEffect, -} from '@angular/core'; +import {Component, computed, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {Combobox} from './combobox'; -import {ComboboxPopup} from './combobox-popup'; -import {ComboboxWidget} from './combobox-widget'; - +import {Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer} from '../combobox'; import {Listbox, Option} from '../listbox'; import {runAccessibilityChecks} from '@angular/cdk/testing/private'; import {Tree, TreeItem, TreeItemGroup} from '../tree'; import {NgTemplateOutlet} from '@angular/common'; -import {Grid, GridRow, GridCell, GridCellWidget} from '../grid'; describe('Combobox', () => { describe('with Listbox', () => { let fixture: ComponentFixture; let inputElement: HTMLInputElement; - const keydown = async (key: string, modifierKeys: {} = {}) => { - await focus(); + const keydown = (key: string, modifierKeys: {} = {}) => { + focus(); inputElement.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -33,99 +21,117 @@ describe('Combobox', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const input = async (value: string) => { - await focus(); + const input = (value: string) => { + focus(); inputElement.value = value; inputElement.dispatchEvent(new Event('input', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (element: HTMLElement, eventInit?: PointerEventInit) => { - await focus(); + const click = (element: HTMLElement, eventInit?: PointerEventInit) => { + focus(); element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const focus = async () => { + const focus = () => { inputElement.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const blur = async (relatedTarget?: EventTarget) => { + const blur = (relatedTarget?: EventTarget) => { inputElement.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const up = async (modifierKeys?: {}) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: {}) => await keydown('ArrowDown', modifierKeys); - const enter = async (modifierKeys?: {}) => await keydown('Enter', modifierKeys); - const escape = async (modifierKeys?: {}) => await keydown('Escape', modifierKeys); + const up = (modifierKeys?: {}) => keydown('ArrowUp', modifierKeys); + const down = (modifierKeys?: {}) => keydown('ArrowDown', modifierKeys); + const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys); + const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys); - async function setupCombobox( - componentType: any = ComboboxListboxExample, - opts: {readonly?: boolean} = {}, + function setupCombobox( + opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}, ) { - fixture = TestBed.createComponent(componentType); + fixture = TestBed.createComponent(ComboboxListboxExample); const testComponent = fixture.componentInstance; + if (opts.filterMode) { + testComponent.filterMode.set(opts.filterMode); + } if (opts.readonly) { testComponent.readonly.set(true); } - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } function defineTestVariables() { - const inputDebugElement = fixture.debugElement.query(By.directive(Combobox)); + const inputDebugElement = fixture.debugElement.query(By.directive(ComboboxInput)); inputElement = inputDebugElement.nativeElement as HTMLInputElement; } function getOption(text: string): HTMLElement | null { - const options = Array.from(document.querySelectorAll('[ngoption]')) as HTMLElement[]; + const options = fixture.debugElement + .queryAll(By.directive(Option)) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); return options.find(option => option.textContent?.trim() === text) || null; } function getOptions(): HTMLElement[] { - return Array.from(document.querySelectorAll('[ngoption]')) as HTMLElement[]; + return fixture.debugElement + .queryAll(By.directive(Option)) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); } afterEach(async () => await runAccessibilityChecks(fixture.nativeElement)); describe('ARIA attributes and roles', () => { - beforeEach(async () => await setupCombobox()); + beforeEach(() => setupCombobox()); it('should have the combobox role on the input', () => { expect(inputElement.getAttribute('role')).toBe('combobox'); }); - it('should have aria-haspopup set to listbox', async () => { - await focus(); + it('should have aria-haspopup set to listbox', () => { + focus(); expect(inputElement.getAttribute('aria-haspopup')).toBe('listbox'); }); - it('should set aria-controls to the listbox id', async () => { - await down(); // Focus on Alabama + it('should set aria-controls to the listbox id', () => { + focus(); const listbox = fixture.debugElement.query(By.directive(Listbox)).nativeElement; expect(inputElement.getAttribute('aria-controls')).toBe(listbox.id); }); - it('should set aria-multiselectable to false on the listbox', async () => { - await down(); // Focus on Alabama - const listbox = fixture.debugElement.query(By.directive(Listbox)).nativeElement; - expect(listbox.getAttribute('aria-multiselectable')).toBe('false'); + it('should set aria-autocomplete to list for manual mode', () => { + expect(inputElement.getAttribute('aria-autocomplete')).toBe('list'); }); - it('should set aria-selected on the selected option', async () => { - await down(); // Focus on Alabama - expect(getOption('Alabama')!.getAttribute('aria-selected')).toBe('false'); - await enter(); // Select Alabama + it('should set aria-autocomplete to list for auto-select mode', () => { + fixture.componentInstance.filterMode.set('auto-select'); + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-autocomplete')).toBe('list'); + }); - await down(); // Reopen popup and focus on Alabama + it('should set aria-autocomplete to both for highlight mode', () => { + fixture.componentInstance.filterMode.set('highlight'); + fixture.detectChanges(); + expect(inputElement.getAttribute('aria-autocomplete')).toBe('both'); + }); + it('should set aria-multiselectable to false on the listbox', () => { + focus(); + const listbox = fixture.debugElement.query(By.directive(Listbox)).nativeElement; + expect(listbox.getAttribute('aria-multiselectable')).toBe('false'); + }); + + it('should set aria-selected on the selected option', () => { + down(); + enter(); expect(getOption('Alabama')!.getAttribute('aria-selected')).toBe('true'); }); @@ -133,10 +139,10 @@ describe('Combobox', () => { expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should toggle aria-expanded when opening and closing', async () => { - await down(); + it('should toggle aria-expanded when opening and closing', () => { + down(); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - await escape(); + escape(); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); @@ -144,58 +150,56 @@ describe('Combobox', () => { expect(inputElement.hasAttribute('aria-activedescendant')).toBe(false); }); - it('should set aria-activedescendant to the active option id', async () => { - await down(); + it('should set aria-activedescendant to the active option id', () => { + down(); const option = getOption('Alabama')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(option.id); }); }); describe('Navigation', () => { - beforeEach(async () => await setupCombobox()); + beforeEach(() => setupCombobox()); - it('should navigate to the first item on ArrowDown', async () => { - await down(); + it('should navigate to the first item on ArrowDown', () => { + down(); const options = getOptions(); - expect(inputElement.getAttribute('aria-activedescendant')).toBe(options[0].id); }); - it('should navigate to the last item on ArrowUp', async () => { - await down(); // Opens the focus on Alabama - await up(); + it('should navigate to the last item on ArrowUp', () => { + up(); const options = getOptions(); expect(inputElement.getAttribute('aria-activedescendant')).toBe( options[options.length - 1].id, ); }); - it('should navigate to the next item on ArrowDown when open', async () => { - await down(); // Open popup - await down(); // Move to next item + it('should navigate to the next item on ArrowDown when open', () => { + down(); + down(); const options = getOptions(); expect(inputElement.getAttribute('aria-activedescendant')).toBe(options[1].id); }); - it('should navigate to the previous item on ArrowUp when open', async () => { - await down(); // Open - await down(); // Move to next item - await up(); // Move back to first item + it('should navigate to the previous item on ArrowUp when open', () => { + down(); + down(); + up(); const options = getOptions(); expect(inputElement.getAttribute('aria-activedescendant')).toBe(options[0].id); }); - it('should navigate to the first item on Home when open', async () => { - await down(); // Open - await down(); // Move to next item - await keydown('Home'); + it('should navigate to the first item on Home when open', () => { + down(); + down(); + keydown('Home'); const options = getOptions(); expect(inputElement.getAttribute('aria-activedescendant')).toBe(options[0].id); }); - it('should navigate to the last item on End when open', async () => { - await down(); // Open - await keydown('End'); + it('should navigate to the last item on End when open', () => { + down(); + keydown('End'); const options = getOptions(); expect(inputElement.getAttribute('aria-activedescendant')).toBe( options[options.length - 1].id, @@ -204,299 +208,265 @@ describe('Combobox', () => { }); describe('Expansion', () => { - beforeEach(async () => await setupCombobox()); + beforeEach(() => setupCombobox()); - it('should open on ArrowDown', async () => { - await focus(); - await keydown('ArrowDown'); + it('should open on ArrowDown', () => { + focus(); + keydown('ArrowDown'); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); }); - it('should close on Escape', async () => { - await down(); - await escape(); + it('should open on ArrowUp', () => { + focus(); + keydown('ArrowUp'); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + }); + + it('should close on Escape', () => { + down(); + escape(); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should close on focusout', async () => { - await focus(); - await blur(); + it('should close on focusout', () => { + focus(); + blur(); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should close on escape and maintain the current input value', async () => { - await setupCombobox(ComboboxListboxHighlightExample); + it('should not close on focusout if focus moves to an element inside the container', () => { + down(); + blur(getOption('Alabama')!); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + }); - await down(); // Use await down() instead of await focus() - await input('Ala'); + it('should close then clear the completion string', () => { + fixture.componentInstance.filterMode.set('highlight'); + focus(); + input('Ala'); expect(inputElement.value).toBe('Alabama'); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - - await escape(); + escape(); expect(inputElement.value).toBe('Alabama'); expect(inputElement.selectionEnd).toBe(7); expect(inputElement.selectionStart).toBe(3); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); // close + escape(); + expect(inputElement.value).toBe(''); // clear input expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should close on enter', async () => { - await down(); - await enter(); + it('should close on enter', () => { + down(); + enter(); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should close on click to select an item', async () => { - await down(); + it('should close on click to select an item', () => { + down(); const fruitItem = getOption('Alabama')!; - await click(fruitItem); + click(fruitItem); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); }); - describe('Selection', () => { - describe('with manual filtering', () => { - beforeEach(async () => await setupCombobox(ComboboxListboxExample)); - it('should select and commit on click', async () => { - await down(); // Use await down() to open + describe('Selection', () => { + describe('when filterMode is "manual"', () => { + beforeEach(() => setupCombobox({filterMode: 'manual'})); + it('should select and commit on click', () => { + click(inputElement); const options = getOptions(); - await click(options[0]); + click(options[0]); + fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); expect(inputElement.value).toBe('Alabama'); }); - it('should select and commit to input on Enter', async () => { - await focus(); - await down(); - - await enter(); + it('should select and commit to input on Enter', () => { + focus(); + down(); + enter(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); expect(inputElement.value).toBe('Alabama'); }); - it('should not select on navigation', async () => { - await down(); - await down(); + it('should not select on navigation', () => { + down(); + down(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); }); - it('should select on focusout if the input text exactly matches an item', async () => { - await focus(); - await input('Alabama'); - await blur(); + it('should select on focusout if the input text exactly matches an item', () => { + focus(); + input('Alabama'); + blur(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); }); - it('should not select on focusout if the input text does not match an item', async () => { - await focus(); - await input('Appl'); - await blur(); + it('should not select on focusout if the input text does not match an item', () => { + focus(); + input('Appl'); + blur(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); expect(inputElement.value).toBe('Appl'); }); }); - describe('with auto-select behavior', () => { - beforeEach(async () => await setupCombobox(ComboboxListboxAutoSelectExample)); - - it('should select and commit on click', async () => { - await down(); // Use await down() to open + describe('when filterMode is "auto-select"', () => { + beforeEach(() => setupCombobox({filterMode: 'auto-select'})); + it('should select and commit on click', () => { + click(inputElement); const options = getOptions(); - await click(options[1]); + click(options[1]); + fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); expect(inputElement.value).toBe('Alaska'); }); - it('should select and commit on Enter', async () => { - await down(); - await down(); - await enter(); + it('should select and commit on Enter', () => { + down(); + down(); + enter(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); expect(inputElement.value).toBe('Alaska'); }); - it('should select on navigation in auto-select', async () => { - await down(); - - expect(fixture.componentInstance.value()).toEqual(['Alabama']); - - await down(); - - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + it('should select on navigation', () => { + down(); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); - await down(); + down(); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + down(); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); }); - it('should select the first option on input', async () => { - await focus(); - await input('W'); - expect(fixture.componentInstance.value()).toEqual(['Washington']); + it('should select the first option on input', () => { + focus(); + input('W'); + + expect(fixture.componentInstance.values()).toEqual(['Washington']); }); - it('should commit the selected option on focusout', async () => { - await focus(); - await input('G'); - await blur(); + it('should commit the selected option on focusout', () => { + focus(); + input('G'); + blur(); expect(inputElement.value).toBe('Georgia'); - expect(fixture.componentInstance.value()).toEqual(['Georgia']); + expect(fixture.componentInstance.values()).toEqual(['Georgia']); }); }); - describe('with highlight behavior', () => { - beforeEach(async () => await setupCombobox(ComboboxListboxHighlightExample)); - - it('should select and commit on click', async () => { - await down(); // Use await down() to open + describe('when filterMode is "highlight"', () => { + beforeEach(() => setupCombobox({filterMode: 'highlight'})); + it('should select and commit on click', () => { + click(inputElement); const options = getOptions(); - await click(options[2]); + click(options[2]); + fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); expect(inputElement.value).toBe('Arizona'); }); - it('should select and commit on Enter', async () => { - await down(); - - await down(); - await down(); - await enter(); + it('should select and commit on Enter', () => { + down(); + down(); + down(); + enter(); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); expect(inputElement.value).toBe('Arizona'); }); - it('should select on navigation', async () => { - await down(); + it('should select on navigation', () => { + down(); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); - // Should auto-select the first option on open - expect(fixture.componentInstance.value()).toEqual(['Alabama']); - - await down(); - - // Should update selection on navigation - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + down(); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); }); - it('should update input value on navigation', async () => { - await down(); - + it('should update input value on navigation', () => { + down(); expect(inputElement.value).toBe('Alabama'); - await down(); - + down(); expect(inputElement.value).toBe('Alaska'); }); - it('should select the first option on input', async () => { - await down(); // Use await down() instead of await focus() - - await input('Cali'); + it('should select the first option on input', () => { + focus(); + input('Cali'); - expect(fixture.componentInstance.value()).toEqual(['California']); + expect(fixture.componentInstance.values()).toEqual(['California']); }); - it('should insert a highlighted completion string on input', async () => { - await down(); // Use await down() instead of await focus() - - await input('A'); + it('should insert a highlighted completion string on input', () => { + focus(); + input('A'); expect(inputElement.value).toBe('Alabama'); expect(inputElement.selectionStart).toBe(1); expect(inputElement.selectionEnd).toBe(7); }); - it('should not insert a completion string on backspace', async () => { - await down(); // Use await down() instead of await focus() - - await input('New'); + it('should not insert a completion string on backspace', () => { + focus(); + input('New'); expect(inputElement.value).toBe('New Hampshire'); expect(inputElement.selectionStart).toBe(3); expect(inputElement.selectionEnd).toBe(13); }); - it('should insert a completion string even if the items are not changed', async () => { - await down(); // Use await down() instead of await focus() - - await input('New'); - await fixture.whenStable(); - await fixture.whenStable(); - - await input('New '); + it('should insert a completion string even if the items are not changed', () => { + focus(); + input('New'); + input('New '); expect(inputElement.value).toBe('New Hampshire'); expect(inputElement.selectionStart).toBe(4); expect(inputElement.selectionEnd).toBe(13); }); - it('should commit the selected option on focusout', async () => { - await down(); // Use await down() instead of await focus() - - await input('Cali'); - - await blur(); + it('should commit the selected option on focusout', () => { + focus(); + input('Cali'); + blur(); expect(inputElement.value).toBe('California'); - expect(fixture.componentInstance.value()).toEqual(['California']); - }); - - it('should resume inserting completion strings on navigation after a backspace deletion', async () => { - await down(); // Open popup - - // 1. Type 'A', completion should pop up 'Alabama' - await input('A'); - expect(inputElement.value).toBe('Alabama'); - - // 2. Simulate Backspace deletion (dispatch InputEvent with deleteContentBackward) - inputElement.value = ''; - inputElement.dispatchEvent( - new InputEvent('input', { - bubbles: true, - inputType: 'deleteContentBackward', - }), - ); - await fixture.whenStable(); - - // Confirm no completion gets inserted during deletion - expect(inputElement.value).toBe(''); - - // 3. Press ArrowDown key to navigate to the next option (Alaska) - await down(); - - // Active descendant navigation resets `isDeleting`, so highlight/completion should successfully populate the current active match! - const options = getOptions(); - expect(inputElement.getAttribute('aria-activedescendant')).toBe(options[1].id); - expect(inputElement.value).toBe('Alaska'); + expect(fixture.componentInstance.values()).toEqual(['California']); }); }); }); + // TODO(wagnermaciel): Add unit tests for disabled options. + describe('Filtering', () => { - it('should lazily render options', async () => { - await setupCombobox(); + it('should lazily render options', () => { + setupCombobox(); expect(getOptions().length).toBe(0); - - await down(); - + focus(); expect(getOptions().length).toBe(50); }); - it('should filter the options based on the input value', async () => { - await setupCombobox(); - await focus(); - await input('New'); + it('should filter the options based on the input value', () => { + setupCombobox(); + focus(); + input('New'); const options = getOptions(); expect(options.length).toBe(4); @@ -506,145 +476,91 @@ describe('Combobox', () => { expect(options[3].textContent?.trim()).toBe('New York'); }); - it('should show no options if nothing matches', async () => { - await setupCombobox(); - await focus(); - await input('xyz'); + it('should show no options if nothing matches', () => { + setupCombobox(); + focus(); + input('xyz'); const options = getOptions(); expect(options.length).toBe(0); }); - it('should show all options when the input is cleared', async () => { - await setupCombobox(); - await focus(); - await input('Alabama'); + it('should show all options when the input is cleared', () => { + setupCombobox(); + focus(); + input('Alabama'); expect(getOptions().length).toBe(1); - await input(''); + input(''); expect(getOptions().length).toBe(50); }); - }); - describe('Interactivity and Disablement (disabled, softDisabled, readonly)', () => { - describe('Readonly', () => { - beforeEach(async () => await setupCombobox(ComboboxListboxExample, {readonly: true})); + it('should determine the highlighted state on open', () => { + setupCombobox({filterMode: 'highlight'}); + focus(); + input('N'); + expect(inputElement.value).toBe('Nebraska'); + expect(inputElement.selectionEnd).toBe(8); + expect(inputElement.selectionStart).toBe(1); + expect(getOptions().length).toBe(8); - it('should set native readonly attribute on editable inputs without assigning disabled', () => { - expect(inputElement.hasAttribute('readonly')).toBe(true); - expect(inputElement.hasAttribute('disabled')).toBe(false); - }); - - it('should suppress typing when readonly', async () => { - await focus(); - inputElement.value = 'New'; - inputElement.dispatchEvent(new Event('input')); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); + escape(); // close + inputElement.selectionStart = 2; // Change highlighting + down(); // open - it('should block expansion on arrow down when readonly', async () => { - await focus(); - await down(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - }); - - describe('Disabled (Soft and Hard)', () => { - beforeEach(async () => await setupCombobox()); - - it('should keep the input focusable by default when disabled', async () => { - fixture.componentInstance.disabled.set(true); - await fixture.whenStable(); - - expect(inputElement.disabled).toBe(false); - expect(inputElement.getAttribute('disabled')).toBeNull(); - expect(inputElement.getAttribute('aria-disabled')).toBe('true'); - }); - - it('should assign readonly when disabled and softDisabled is true on editable inputs', async () => { - fixture.componentInstance.disabled.set(true); - await fixture.whenStable(); - - expect(inputElement.hasAttribute('readonly')).toBe(true); - }); - - it('should block interactions when disabled', async () => { - fixture.componentInstance.disabled.set(true); - await fixture.whenStable(); - - await focus(); - await keydown('ArrowDown'); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should assign disabled, readonly, and aria-disabled when hard-disabled', async () => { - fixture.componentInstance.disabled.set(true); - fixture.componentInstance.softDisabled.set(false); - await fixture.whenStable(); - - expect(inputElement.disabled).toBe(true); - expect(inputElement.getAttribute('disabled')).toBe(''); - expect(inputElement.hasAttribute('readonly')).toBe(true); - expect(inputElement.getAttribute('aria-disabled')).toBe('true'); - }); - - it('should respect user-defined tabindex when softDisabled is true', async () => { - fixture.componentInstance.disabled.set(true); - fixture.componentInstance.tabIndex.set(0); - await fixture.whenStable(); - - expect(inputElement.getAttribute('tabindex')).toBe('0'); - }); + expect(inputElement.value).toBe('Nebraska'); + expect(inputElement.selectionEnd).toBe(8); + expect(inputElement.selectionStart).toBe(2); + expect(getOptions().length).toBe(6); - it('should respect user-defined tabindex when not disabled', async () => { - fixture.componentInstance.tabIndex.set(0); - await fixture.whenStable(); + escape(); // close + inputElement.selectionStart = 3; // Change highlighting + down(); // open - expect(inputElement.getAttribute('tabindex')).toBe('0'); - }); - - it('should default to tabindex 0 when not disabled', async () => { - await fixture.whenStable(); - expect(inputElement.getAttribute('tabindex')).toBe('0'); - }); - - it('should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex', async () => { - fixture.componentInstance.disabled.set(true); - fixture.componentInstance.softDisabled.set(false); - fixture.componentInstance.tabIndex.set(0); - await fixture.whenStable(); - - expect(inputElement.getAttribute('tabindex')).toBe('-1'); - }); + expect(getOptions().length).toBe(1); }); }); - describe('Always Expanded', () => { - beforeEach(async () => await setupCombobox()); - - it('should not close on escape when alwaysExpanded is true', async () => { - fixture.componentInstance.alwaysExpanded.set(true); - await fixture.whenStable(); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + describe('Readonly', () => { + beforeEach(() => setupCombobox({readonly: true})); - await escape(); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + it('should close on selection', () => { + focus(); + down(); + click(getOption('Alabama')!); + expect(inputElement.value).toBe('Alabama'); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should automatically report as expanded when alwaysExpanded is true', async () => { - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - fixture.componentInstance.alwaysExpanded.set(true); - await fixture.whenStable(); + it('should close on escape', () => { + focus(); + down(); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + escape(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); }); + + // describe('with programmatic value changes', () => { + // // TODO(wagnermaciel): Figure out if there's a way to automatically update the + // // input value when the popup value signal is updated programmatically. + // it('should update the selected item when the value is set programmatically', () => { + // setupCombobox(); + // focus(); + // fixture.componentInstance.value.set(['Banana']); + // fixture.detectChanges(); + // expect(fixture.componentInstance.values()).toEqual(['Banana']); + // const bananaOption = getOption('Banana')!; + // expect(bananaOption.getAttribute('aria-selected')).toBe('true'); + // }); + // }); }); describe('with Tree', () => { let fixture: ComponentFixture; let inputElement: HTMLInputElement; - const keydown = async (key: string, modifierKeys: {} = {}) => { - await focus(); + const keydown = (key: string, modifierKeys: {} = {}) => { + focus(); inputElement.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -652,65 +568,75 @@ describe('Combobox', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const input = async (value: string) => { - await focus(); + const input = (value: string, opts: {backspace?: boolean} = {}) => { + focus(); inputElement.value = value; - inputElement.dispatchEvent(new Event('input', {bubbles: true})); - await fixture.whenStable(); + const event = opts.backspace + ? new InputEvent('input', {inputType: 'deleteContentBackward', bubbles: true}) + : new InputEvent('input', {bubbles: true}); + inputElement.dispatchEvent(event); + fixture.detectChanges(); }; - const click = async (element: HTMLElement, eventInit?: PointerEventInit) => { - await focus(); + const click = (element: HTMLElement, eventInit?: PointerEventInit) => { + focus(); element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const focus = async () => { + const focus = () => { inputElement.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const blur = async (relatedTarget?: EventTarget) => { + const blur = (relatedTarget?: EventTarget) => { inputElement.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const up = async (modifierKeys?: {}) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: {}) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: {}) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: {}) => await keydown('ArrowRight', modifierKeys); - const enter = async (modifierKeys?: {}) => await keydown('Enter', modifierKeys); - const escape = async (modifierKeys?: {}) => await keydown('Escape', modifierKeys); + const up = (modifierKeys?: {}) => keydown('ArrowUp', modifierKeys); + const down = (modifierKeys?: {}) => keydown('ArrowDown', modifierKeys); + const left = (modifierKeys?: {}) => keydown('ArrowLeft', modifierKeys); + const right = (modifierKeys?: {}) => keydown('ArrowRight', modifierKeys); + const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys); + const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys); - async function setupCombobox(opts: {readonly?: boolean} = {}) { + function setupCombobox( + opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}, + ) { fixture = TestBed.createComponent(ComboboxTreeExample); const testComponent = fixture.componentInstance; + if (opts.filterMode) { + testComponent.filterMode.set(opts.filterMode); + } if (opts.readonly) { testComponent.readonly.set(true); } - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } function defineTestVariables() { - const inputDebugElement = fixture.debugElement.query(By.directive(Combobox)); + const inputDebugElement = fixture.debugElement.query(By.directive(ComboboxInput)); inputElement = inputDebugElement.nativeElement as HTMLInputElement; } function getTreeItem(text: string): HTMLElement | null { - const items = Array.from( - fixture.nativeElement.querySelectorAll('[ngTreeItem]'), - ) as HTMLElement[]; - return items.find(item => item.textContent?.trim().startsWith(text)) || null; + const items = fixture.debugElement + .queryAll(By.directive(TreeItem)) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); + return items.find(item => item.textContent?.trim() === text) || null; } function getTreeItems(): HTMLElement[] { - return Array.from(fixture.nativeElement.querySelectorAll('[ngTreeItem]')) as HTMLElement[]; + return fixture.debugElement + .queryAll(By.directive(TreeItem)) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); } function getVisibleTreeItems(): HTMLElement[] { @@ -727,536 +653,465 @@ describe('Combobox', () => { }); } - afterEach(async () => { - await runAccessibilityChecks(fixture.nativeElement); - }); + afterEach(async () => await runAccessibilityChecks(fixture.nativeElement)); describe('ARIA attributes and roles', () => { - beforeEach(async () => await setupCombobox()); + beforeEach(() => setupCombobox()); - it('should have aria-haspopup set to tree', async () => { - await focus(); + it('should have aria-haspopup set to tree', () => { + focus(); expect(inputElement.getAttribute('aria-haspopup')).toBe('tree'); }); - it('should set aria-controls to the tree id', async () => { - await down(); + it('should set aria-controls to the tree id', () => { + down(); const tree = fixture.debugElement.query(By.directive(Tree)).nativeElement; expect(inputElement.getAttribute('aria-controls')).toBe(tree.id); }); - it('should set aria-selected on the selected tree item', async () => { - await down(); + it('should set aria-selected on the selected tree item', () => { + down(); + enter(); + const item = getTreeItem('Winter')!; - await enter(); expect(item.getAttribute('aria-selected')).toBe('true'); }); - it('should toggle aria-expanded on parent nodes', async () => { - await down(); + it('should toggle aria-expanded on parent nodes', () => { + down(); const item = getTreeItem('Winter')!; expect(item.getAttribute('aria-expanded')).toBe('false'); - await right(); // Opens Winter + right(); expect(item.getAttribute('aria-expanded')).toBe('true'); - await left(); // Closes Winter + left(); expect(item.getAttribute('aria-expanded')).toBe('false'); }); }); describe('Navigation', () => { - beforeEach(async () => await setupCombobox()); + beforeEach(() => setupCombobox()); - it('should navigate to the first focusable item on ArrowDown', async () => { - await down(); // Winter + it('should navigate to the first focusable item on ArrowDown', () => { + down(); const item = getTreeItem('Winter')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should navigate to the last focusable item on ArrowUp', async () => { - await down(); // Winter - await up(); // Fall + it('should navigate to the last focusable item on ArrowUp', () => { + up(); const item = getTreeItem('Fall')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should navigate to the next focusable item on ArrowDown when open', async () => { - await down(); // Winter - await down(); // Spring + it('should navigate to the next focusable item on ArrowDown when open', () => { + down(); + down(); const item = getTreeItem('Spring')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should navigate to the previous item on ArrowUp when open', async () => { - await down(); // Winter - await down(); // Spring - await down(); // Summer - await down(); // Fall - await up(); // Summer + it('should navigate to the previous item on ArrowUp when open', () => { + up(); + up(); const item = getTreeItem('Summer')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should expand a closed node on ArrowRight', async () => { - await down(); // Winter + it('should expand a closed node on ArrowRight', () => { + down(); expect(getVisibleTreeItems().length).toBe(4); - await right(); // Expand Winter + right(); + fixture.detectChanges(); expect(getVisibleTreeItems().length).toBe(7); expect(getTreeItem('January')).not.toBeNull(); }); - it('should navigate to the next item on ArrowRight when already expanded', async () => { - await down(); // Winter - await right(); // Expand Winter - await right(); // December - + it('should navigate to the next item on ArrowRight when already expanded', () => { + down(); + right(); + right(); const item = getTreeItem('December')!; - expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should collapse an open node on ArrowLeft', async () => { - await down(); // Winter - await right(); // Winter Expanded + it('should collapse an open node on ArrowLeft', () => { + down(); + right(); + fixture.detectChanges(); expect(getVisibleTreeItems().length).toBe(7); - await left(); // Winter Collapsed + left(); + fixture.detectChanges(); expect(getVisibleTreeItems().length).toBe(4); - const item = getTreeItem('Winter')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should navigate to the parent node on ArrowLeft when in a child node', async () => { - await down(); // Winter - await right(); // Expand Winter - await right(); // December - + it('should navigate to the parent node on ArrowLeft when in a child node', () => { + down(); + right(); + right(); const item1 = getTreeItem('December')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item1.id); - - await left(); - + left(); const item2 = getTreeItem('Winter')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item2.id); }); - it('should navigate to the first focusable item on Home when open', async () => { - await down(); - await down(); - await keydown('Home'); - + it('should navigate to the first focusable item on Home when open', () => { + up(); + keydown('Home'); const item = getTreeItem('Winter')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(item.id); }); - it('should navigate to the last focusable item on End when open', async () => { - await down(); - await down(); - await keydown('End'); - + it('should navigate to the last focusable item on End when open', () => { + down(); + keydown('End'); const grainsItem = getTreeItem('Fall')!; expect(inputElement.getAttribute('aria-activedescendant')).toBe(grainsItem.id); }); }); - describe('Expansion', () => { - beforeEach(async () => await setupCombobox()); - - it('should open on ArrowDown', async () => { - await focus(); - await keydown('ArrowDown'); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - }); - - it('should close on Escape', async () => { - await down(); - await escape(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should close on focusout', async () => { - await focus(); - await blur(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should close on escape', async () => { - await focus(); - await input('Mar'); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - await escape(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should close on enter', async () => { - await down(); - await enter(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should close on click to select an item', async () => { - await down(); - await click(getTreeItem('Spring')!); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - }); - describe('Selection', () => { - describe('with manual filtering', () => { - beforeEach(async () => await setupCombobox()); + describe('when filterMode is "manual"', () => { + beforeEach(() => setupCombobox({filterMode: 'manual'})); - it('should select and commit on click', async () => { - await click(inputElement); + it('should select and commit on click', () => { + click(inputElement); // Iterate to the parent node and expand it so the child is visible - await down(); // Winter - await down(); // Spring - await right(); // Expand Spring + down(); // Winter + down(); // Spring + right(); // Expand Spring + fixture.detectChanges(); const item = getTreeItem('April')!; - await click(item); + click(item); + fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['April']); + expect(fixture.componentInstance.values()).toEqual(['April']); expect(inputElement.value).toBe('April'); }); - it('should select and commit to input on Enter', async () => { - await down(); - await enter(); + it('should select and commit to input on Enter', () => { + down(); + enter(); - expect(fixture.componentInstance.value()).toEqual(['Winter']); + expect(fixture.componentInstance.values()).toEqual(['Winter']); expect(inputElement.value).toBe('Winter'); }); - it('should select on focusout if the input text exactly matches an item', async () => { - await focus(); - await input('November'); - await blur(); + it('should select on focusout if the input text exactly matches an item', () => { + focus(); + input('November'); + blur(); - expect(fixture.componentInstance.value()).toEqual(['November']); + expect(fixture.componentInstance.values()).toEqual(['November']); }); - it('should not select on navigation', async () => { - await down(); - await down(); + it('should not select on navigation', () => { + down(); + down(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); }); - it('should not select on focusout if the input text does not match an item', async () => { - await focus(); - await input('Appl'); - await blur(); + it('should not select on focusout if the input text does not match an item', () => { + focus(); + input('Appl'); + blur(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); expect(inputElement.value).toBe('Appl'); }); }); - }); - describe('Filtering', () => { - beforeEach(async () => await setupCombobox()); + describe('when filterMode is "auto-select"', () => { + beforeEach(() => setupCombobox({filterMode: 'auto-select'})); - it('should lazily render options', async () => { - expect(getTreeItems().length).toBe(0); + it('should select and commit on click', () => { + click(inputElement); + down(); + right(); + const item = getTreeItem('February')!; + click(item); + fixture.detectChanges(); - await focus(); - await down(); - // Mutate dataSource to expand all - fixture.componentInstance.dataSource().forEach(node => (node.expanded = true)); + expect(fixture.componentInstance.values()).toEqual(['February']); + expect(inputElement.value).toBe('February'); + }); - // Force computed signal to re-evaluate by updating dataSource reference - fixture.componentInstance.dataSource.set([...fixture.componentInstance.dataSource()]); - await fixture.whenStable(); + it('should select and commit on Enter', () => { + down(); + down(); + enter(); - expect(getTreeItems().length).toBe(16); - }); + expect(fixture.componentInstance.values()).toEqual(['Spring']); + expect(inputElement.value).toBe('Spring'); + }); - it('should filter the options based on the input value', async () => { - await focus(); - await input('Summer'); + it('should select on navigation', () => { + down(); + expect(fixture.componentInstance.values()).toEqual(['Winter']); - let items = getVisibleTreeItems(); - expect(items.length).toBe(1); - expect(items[0].textContent?.trim()).toBe('Summer'); - }); + down(); + expect(fixture.componentInstance.values()).toEqual(['Spring']); + }); - it('should render parents if a child matches', async () => { - await focus(); - await input('January'); + it('should select the first option on input', () => { + focus(); + input('Dec'); + expect(fixture.componentInstance.values()).toEqual(['December']); + }); - let items = getVisibleTreeItems(); - expect(items.length).toBe(2); - expect(items[0].textContent?.trim()).toBe('Winter'); - expect(items[1].textContent?.trim()).toBe('January'); - }); + it('should commit the selected option on focusout', () => { + focus(); + input('Jun'); + blur(); - it('should show no options if nothing matches', async () => { - await focus(); - await input('xyz'); - expect(getVisibleTreeItems().length).toBe(0); + expect(inputElement.value).toBe('June'); + expect(fixture.componentInstance.values()).toEqual(['June']); + }); }); - it('should show all options when the input is cleared', async () => { - await focus(); - await input('Winter'); - expect(getVisibleTreeItems().length).toBe(1); + describe('when filterMode is "highlight"', () => { + beforeEach(() => setupCombobox({filterMode: 'highlight'})); - await input(''); - expect(getVisibleTreeItems().length).toBe(4); - }); + it('should select and commit on click', () => { + click(inputElement); + down(); + right(); + const item = getTreeItem('February')!; + click(item); + fixture.detectChanges(); - it('should expand all nodes when filtering', async () => { - await focus(); - await down(); + expect(fixture.componentInstance.values()).toEqual(['February']); + expect(inputElement.value).toBe('February'); + }); - expect(getVisibleTreeItems().length).toBe(4); + it('should select and commit on Enter', () => { + down(); + down(); + enter(); - await input('J'); + expect(fixture.componentInstance.values()).toEqual(['Spring']); + expect(inputElement.value).toBe('Spring'); + }); - expect(getTreeItem('Winter')!.getAttribute('aria-expanded')).toBe('true'); - expect(getTreeItem('Summer')!.getAttribute('aria-expanded')).toBe('true'); - }); - }); - }); + it('should select on navigation', () => { + down(); + expect(fixture.componentInstance.values()).toEqual(['Winter']); - describe('with Grid', () => { - let fixture: ComponentFixture; - let inputElement: HTMLInputElement; + down(); + expect(fixture.componentInstance.values()).toEqual(['Spring']); + }); - const keydown = async (key: string, modifierKeys: {} = {}) => { - await focus(); - inputElement.dispatchEvent( - new KeyboardEvent('keydown', { - key, - bubbles: true, - ...modifierKeys, - }), - ); - await fixture.whenStable(); - }; + it('should update input value on navigation', () => { + down(); + expect(inputElement.value).toBe('Winter'); - const focus = async () => { - inputElement.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - }; + down(); + expect(inputElement.value).toBe('Spring'); + }); - const blur = async (relatedTarget?: EventTarget) => { - inputElement.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); - }; + it('should select the first option on input', () => { + focus(); + input('Sept'); - const up = async (modifierKeys?: {}) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: {}) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: {}) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: {}) => await keydown('ArrowRight', modifierKeys); - const enter = async (modifierKeys?: {}) => await keydown('Enter', modifierKeys); - const escape = async (modifierKeys?: {}) => await keydown('Escape', modifierKeys); - const home = async (modifierKeys?: {}) => await keydown('Home', modifierKeys); - const end = async (modifierKeys?: {}) => await keydown('End', modifierKeys); - - async function setupCombobox() { - fixture = TestBed.createComponent(ComboboxGridExample); - await fixture.whenStable(); - const inputDebugElement = fixture.debugElement.query(By.directive(Combobox)); - inputElement = inputDebugElement.nativeElement as HTMLInputElement; - } + expect(fixture.componentInstance.values()).toEqual(['September']); + }); - beforeEach(async () => await setupCombobox()); + it('should insert a highlighted completion string on input', () => { + focus(); + input('Feb'); - describe('ARIA attributes and roles', () => { - beforeEach(async () => await setupCombobox()); + expect(inputElement.value).toBe('February'); + expect(inputElement.selectionStart).toBe(3); + expect(inputElement.selectionEnd).toBe(8); + }); - it('should have the combobox role on the input', () => { - expect(inputElement.getAttribute('role')).toBe('combobox'); - }); + it('should commit the selected option on focusout', () => { + focus(); + input('Jan'); + blur(); - it('should have aria-haspopup set to grid', async () => { - await focus(); - expect(inputElement.getAttribute('aria-haspopup')).toBe('grid'); + expect(inputElement.value).toBe('January'); + expect(fixture.componentInstance.values()).toEqual(['January']); + }); }); + }); - it('should set aria-controls to the grid id', async () => { - await down(); - const grid = fixture.debugElement.query(By.directive(Grid)).nativeElement; - expect(inputElement.getAttribute('aria-controls')).toBe(grid.id); - }); + describe('Expansion', () => { + beforeEach(() => setupCombobox()); - it('should toggle aria-expanded when opening and closing', async () => { - await down(); + it('should open on ArrowDown', () => { + focus(); + keydown('ArrowDown'); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - await escape(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should set aria-activedescendant to the active grid cell id', async () => { - await focus(); - await down(); // Open popup - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-label'); + it('should open on ArrowUp', () => { + focus(); + keydown('ArrowUp'); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); }); - }); - - it('should navigate up and down with grid navigation', async () => { - await focus(); - await down(); // Open popup - - await down(); // Navigate down to 'Bird-label' - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Bird-label'); - await up(); // Navigate back up to 'Antelope-label' - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-label'); - }); + it('should close on Escape', () => { + down(); + escape(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); - it('should navigate left and right with grid navigation', async () => { - await focus(); - await down(); // Open popup + it('should close on focusout', () => { + focus(); + blur(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); - await right(); // Move right to 'Antelope-delete' + it('should not close on focusout if focus moves to an element inside the container', () => { + down(); + blur(getTreeItem('Spring')!); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + }); - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-delete'); + it('should close then clear the completion string', () => { + fixture.componentInstance.filterMode.set('highlight'); + focus(); + input('Mar'); + expect(inputElement.value).toBe('March'); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + escape(); + expect(inputElement.value).toBe('March'); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); // close + escape(); + expect(inputElement.value).toBe(''); // clear input + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); - await left(); // Move back left to 'Antelope-label' + it('should close on enter', () => { + down(); + enter(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-label'); + it('should close on click to select an item', () => { + down(); + click(getTreeItem('Spring')!); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); }); - it('should navigate to the start of the row on Home', async () => { - await focus(); - await down(); // Open popup - - await right(); // Move right to 'Antelope-delete' - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-delete'); - - await home(); // Move back to 'Antelope-label' - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-label'); - }); + // TODO(wagnermaciel): Add unit tests for disabled options. - it('should navigate to the end of the row on End', async () => { - await focus(); - await down(); // Open popup + describe('Filtering', () => { + beforeEach(() => setupCombobox()); - await end(); // Move to end of row ('Antelope-delete') + it('should lazily render options', () => { + expect(getTreeItems().length).toBe(0); + focus(); + expect(getTreeItems().length).toBe(16); + }); - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Antelope-delete'); - }); + it('should filter the options based on the input value', () => { + focus(); + input('Summer'); - it('should update aria-activedescendant with grid navigation', async () => { - await focus(); - await down(); // Open popup + let items = getVisibleTreeItems(); + expect(items.length).toBe(1); + expect(items[0].textContent?.trim()).toBe('Summer'); + }); - await down(); // Navigate down + it('should render parents if a child matches', () => { + focus(); + input('January'); - // The active item is 'Bird' because we navigated down once more - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Bird-label'); + let items = getVisibleTreeItems(); + expect(items.length).toBe(2); + expect(items[0].textContent?.trim()).toBe('Winter'); + expect(items[1].textContent?.trim()).toBe('January'); + }); - await right(); // Move right to delete button + it('should show no options if nothing matches', () => { + focus(); + input('xyz'); + expect(getVisibleTreeItems().length).toBe(0); + }); - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Bird-delete'); + it('should show all options when the input is cleared', () => { + focus(); + input('Winter'); + expect(getVisibleTreeItems().length).toBe(1); - await down(); // Move down to next row + input('', {backspace: true}); + fixture.detectChanges(); + expect(getVisibleTreeItems().length).toBe(4); + }); - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Cat-delete'); - }); + it('should expand all nodes when filtering', () => { + focus(); + expect(getVisibleTreeItems().length).toBe(4); - it('should remove an item when delete is pressed in the delete cell', async () => { - await down(); // On Antelope - await right(); // Move right to delete button - await enter(); // Click delete button - expect(fixture.componentInstance.items()).not.toContain('Antelope'); + input('J'); + expect(getTreeItem('Winter')!.getAttribute('aria-expanded')).toBe('true'); + expect(getTreeItem('Summer')!.getAttribute('aria-expanded')).toBe('true'); + }); }); - it('should filter items and maintain selection', async () => { - await down(); // Antelope - await enter(); // Select active item - - expect(fixture.componentInstance.searchString()).toBe('Antelope'); - - inputElement.value = ''; - inputElement.dispatchEvent(new Event('input', {bubbles: true})); - await fixture.whenStable(); - - expect(fixture.componentInstance.searchString()).toBe(''); - - await down(); // Go to BirdLabel - - expect(inputElement.getAttribute('aria-activedescendant')).toBe('Bird-label'); + describe('with programmatic value changes', () => { + // TODO(wagnermaciel): Figure out if there's a way to automatically update the + // input value when the popup value signal is updated programmatically. + it('should update the selected item when the value is set programmatically', () => { + setupCombobox(); + focus(); + fixture.componentInstance.values.set(['August']); + fixture.detectChanges(); + expect(fixture.componentInstance.values()).toEqual(['August']); + expect(getTreeItem('August')!.getAttribute('aria-selected')).toBe('true'); + }); }); - describe('Expansion', () => { - beforeEach(async () => await setupCombobox()); + describe('Readonly', () => { + beforeEach(() => setupCombobox({readonly: true})); - it('should close on Escape', async () => { - await down(); - await escape(); + it('should close on selection', () => { + focus(); + down(); + right(); + right(); + enter(); + expect(inputElement.value).toBe('December'); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); - it('should close on focusout', async () => { - await focus(); - await blur(); - expect(inputElement.getAttribute('aria-expanded')).toBe('false'); - }); - - it('should close on enter', async () => { - await down(); - await enter(); + it('should close on escape', () => { + focus(); + down(); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + escape(); expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); }); - - describe('Selection', () => { - beforeEach(async () => await setupCombobox()); - - it('should select and commit on click', async () => { - await focus(); - await down(); // Open popup - - const gridCells = fixture.nativeElement.querySelectorAll('[ngGridCellWidget]'); - gridCells[0].dispatchEvent(new PointerEvent('click', {bubbles: true})); - await fixture.whenStable(); - - expect(fixture.componentInstance.selectedItem()).toBe('Antelope'); - expect(inputElement.value).toBe('Antelope'); - }); - - it('should not select on navigation', async () => { - await focus(); - await down(); // Open popup - - await down(); // Move row down - - expect(fixture.componentInstance.selectedItem()).toBeNull(); - }); - }); }); }); @Component({ template: ` -
+
- -
+ +
@for (option of options(); track option) {
{
`, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], + imports: [Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer, Listbox, Option], }) class ComboboxListboxExample { readonly = signal(false); - disabled = signal(false); - softDisabled = signal(true); - alwaysExpanded = signal(false); - tabIndex = signal(undefined); - popupExpanded = signal(false); searchString = signal(''); - value = signal([]); + values = signal([]); + filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); options = computed(() => states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), ); - - onCommit() { - const val = this.value(); - if (val.length > 0) { - this.searchString.set(val[0]); - } - this.popupExpanded.set(false); - } - - onBlur() { - const search = this.searchString().trim().toLowerCase(); - if (!search) return; - - const match = states.find(state => state.toLowerCase().startsWith(search)); - if (match) { - this.value.set([match]); - this.searchString.set(match); - } - } -} - -interface TreeNode { - name: string; - children?: TreeNode[]; - expanded?: boolean; -} - -function getTreeNodes(): TreeNode[] { - return [ - { - name: 'Winter', - expanded: false, - children: [{name: 'December'}, {name: 'January'}, {name: 'February'}], - }, - { - name: 'Spring', - expanded: false, - children: [{name: 'March'}, {name: 'April'}, {name: 'May'}], - }, - { - name: 'Summer', - expanded: false, - children: [{name: 'June'}, {name: 'July'}, {name: 'August'}], - }, - { - name: 'Fall', - expanded: false, - children: [{name: 'September'}, {name: 'October'}, {name: 'November'}], - }, - ]; } @Component({ template: ` -
+
- -
    + +
      {{ node.name }} @@ -1388,8 +1189,8 @@ function getTreeNodes(): TreeNode[] { `, imports: [ Combobox, - ComboboxPopup, - ComboboxWidget, + ComboboxInput, + ComboboxPopupContainer, Tree, TreeItem, TreeItemGroup, @@ -1397,34 +1198,11 @@ function getTreeNodes(): TreeNode[] { ], }) class ComboboxTreeExample { - readonly tree = viewChild(Tree); - readonly = signal(false); - disabled = signal(false); - popupExpanded = signal(false); searchString = signal(''); - value = signal([]); - readonly dataSource = signal(getTreeNodes()); - nodes = computed(() => { - const res = this.filterTreeNodes(this.dataSource()); - return res; - }); - - onCommit() { - const selected = this.value(); - if (selected.length > 0) { - this.searchString.set(selected[0]); - } - this.popupExpanded.set(false); - } - - onBlur() { - const flatNodes = this.flattenTreeNodes(this.dataSource()); - const match = flatNodes.find(n => n.name.toLowerCase() === this.searchString().toLowerCase()); - if (match) { - this.value.set([match.name]); - } - } + values = signal([]); + nodes = computed(() => this.filterTreeNodes(TREE_NODES)); + filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); firstMatch = computed(() => { const flatNodes = this.flattenTreeNodes(this.nodes()); @@ -1432,44 +1210,17 @@ class ComboboxTreeExample { return node?.name; }); - constructor() { - afterRenderEffect(() => { - const active = this.tree()?._pattern.inputs.activeItem(); - if (active) { - untracked(() => { - active.element()?.scrollIntoView({block: 'nearest'}); - }); - } - }); - } - flattenTreeNodes(nodes: TreeNode[]): TreeNode[] { return nodes.flatMap(node => { return node.children ? [node, ...this.flattenTreeNodes(node.children)] : [node]; }); } - deepCopyNodes(nodes: TreeNode[]): TreeNode[] { - return nodes.map(node => ({ - ...node, - children: node.children ? this.deepCopyNodes(node.children) : undefined, - })); - } - filterTreeNodes(nodes: TreeNode[]): TreeNode[] { - const search = this.searchString().trim().toLowerCase(); - if (!search) { - return nodes; - } - return nodes.reduce((acc, node) => { const children = node.children ? this.filterTreeNodes(node.children) : undefined; if (this.isMatch(node) || (children && children.length > 0)) { - acc.push({ - ...node, - children, - expanded: children && children.length > 0 ? true : node.expanded, - }); + acc.push({...node, children}); } return acc; }, [] as TreeNode[]); @@ -1480,6 +1231,30 @@ class ComboboxTreeExample { } } +interface TreeNode { + name: string; + children?: TreeNode[]; +} + +const TREE_NODES = [ + { + name: 'Winter', + children: [{name: 'December'}, {name: 'January'}, {name: 'February'}], + }, + { + name: 'Spring', + children: [{name: 'March'}, {name: 'April'}, {name: 'May'}], + }, + { + name: 'Summer', + children: [{name: 'June'}, {name: 'July'}, {name: 'August'}], + }, + { + name: 'Fall', + children: [{name: 'September'}, {name: 'October'}, {name: 'November'}], + }, +]; + const states = [ 'Alabama', 'Alaska', @@ -1532,187 +1307,3 @@ const states = [ 'Wisconsin', 'Wyoming', ]; - -@Component({ - template: ` -
      - - - -
      - @for (item of filteredItems(); track item; let i = $index) { -
      -
      - -
      -
      - -
      -
      - } -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Grid, GridRow, GridCell, GridCellWidget], -}) -class ComboboxGridExample { - popupExpanded = signal(false); - searchString = signal(''); - selectedItem = signal(null); - - items = signal(['Antelope', 'Bird', 'Cat', 'Dog']); - - filteredItems = computed(() => { - const search = this.searchString().toLowerCase(); - return this.items().filter(item => item.toLowerCase().includes(search)); - }); - - selectItem(item: string) { - this.selectedItem.set(item); - this.searchString.set(item); - this.popupExpanded.set(false); - } - - removeItem(itemToRemove: string) { - this.items.update(items => items.filter(item => item !== itemToRemove)); - } -} - -@Component({ - template: ` -
      - - - -
      - @for (option of options(); track option) { -
      - {{option}} -
      - } -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], -}) -class ComboboxListboxAutoSelectExample { - readonly = signal(false); - disabled = signal(false); - popupExpanded = signal(false); - searchString = signal(''); - value = signal([]); - - options = computed(() => - states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), - ); - - onInput() { - const filtered = this.options(); - if (filtered.length > 0) { - this.value.set([filtered[0]]); - } - } - - onCommit() { - const val = this.value(); - if (val.length > 0) { - this.searchString.set(val[0]); - } - this.popupExpanded.set(false); - } - - onBlur() { - const search = this.searchString().trim().toLowerCase(); - if (!search) return; - - const match = states.find(state => state.toLowerCase().startsWith(search)); - if (match) { - this.value.set([match]); - this.searchString.set(match); - } - } -} - -@Component({ - template: ` -
      - - - -
      - @for (option of options(); track option) { -
      - {{option}} -
      - } -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], -}) -class ComboboxListboxHighlightExample { - readonly combobox = viewChild(Combobox); - readonly = signal(false); - disabled = signal(false); - popupExpanded = signal(false); - searchString = signal(''); - value = signal([]); - readonly activeDescendantValue = signal(undefined); - - options = computed(() => - states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), - ); - - constructor() { - afterRenderEffect(() => { - const id = this.combobox()?._pattern.activeDescendant(); - if (id) { - const el = document.getElementById(id); - this.activeDescendantValue.set(el?.textContent?.trim()); - } else { - this.activeDescendantValue.set(undefined); - } - }); - } - - onCommit() { - const val = this.value(); - if (val.length > 0) { - this.searchString.set(val[0]); - } - this.popupExpanded.set(false); - } -} diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts index d763eabe1595..e5aa15c5b54d 100644 --- a/src/aria/combobox/combobox.ts +++ b/src/aria/combobox/combobox.ts @@ -10,154 +10,153 @@ import { afterRenderEffect, booleanAttribute, computed, + contentChild, Directive, ElementRef, inject, input, - model, - OnInit, signal, - Renderer2, } from '@angular/core'; -import {DeferredContentAware, ComboboxPattern, tabIndexTransform} from '@angular/aria/private'; -import type {ComboboxPopup} from './combobox-popup'; +import {DeferredContentAware, ComboboxPattern} from '../private'; +import {Directionality} from '@angular/cdk/bidi'; +import {COMBOBOX} from './combobox-tokens'; +import {ComboboxPopup} from './combobox-popup'; /** - * A directive that coordinates a combobox trigger element and its associated popup widget. + * The container element that wraps a combobox input and popup, and orchestrates its behavior. * - * The `ngCombobox` directive is applied directly to the interactive trigger element, which can be - * either an editable `` (for search/autocomplete behaviors) or a non-editable element like - * a `
      ` (for custom select dropdowns). It manages focus and expansion states, coordinates autocomplete - * suggestions (if editable), and forwards navigation keys down into the active popup. + * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its + * behavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which + * is defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the + * `CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`. * - * ### Example 1: Editable Autocomplete Input * ```html - * + *
      + * * - * - *
      - *
      First Option
      - *
      Second Option
      - *
      - *
      + * + *
      + * @for (option of filteredOptions(); track option) { + *
      + * {{option}} + *
      + * } + *
      + *
      + *
      * ``` * - * ### Example 2: Non-Editable Custom Select Dropdown - * ```html - *
      - * {{selectedValue}} - *
      + * @developerPreview 21.0 * - * - *
      - *
      First Option
      - *
      Second Option
      - *
      - *
      - * ``` + * @see [Combobox](guide/aria/combobox) + * @see [Select](guide/aria/select) + * @see [Multiselect](guide/aria/multiselect) + * @see [Autocomplete](guide/aria/autocomplete) */ @Directive({ selector: '[ngCombobox]', exportAs: 'ngCombobox', + hostDirectives: [ + { + directive: DeferredContentAware, + inputs: ['preserveContent'], + }, + ], host: { - 'role': 'combobox', - '[attr.aria-autocomplete]': '_pattern.autocomplete()', - '[attr.aria-disabled]': '_pattern.disabled()', - '[attr.aria-readonly]': '_pattern.ariaReadonly()', - '[attr.aria-expanded]': '_pattern.isExpanded()', - '[attr.aria-activedescendant]': '_pattern.activeDescendant()', - '[attr.aria-controls]': '_pattern.popupId()', - '[attr.aria-haspopup]': '_pattern.popupType()', - '[attr.tabindex]': - 'disabled() && !softDisabled() ? -1 : (tabIndex() !== undefined ? tabIndex() : 0)', - '[attr.disabled]': '_pattern.nativeDisabled()', - '[attr.readonly]': '_pattern.nativeReadonly()', + '[attr.data-expanded]': 'expanded()', + '(input)': '_pattern.onInput($event)', '(keydown)': '_pattern.onKeydown($event)', - '(focusin)': '_pattern.onFocusin()', - '(focusout)': '_pattern.onFocusout()', '(click)': '_pattern.onClick($event)', - '(input)': '_pattern.onInput($event)', + '(focusin)': '_pattern.onFocusIn()', + '(focusout)': '_pattern.onFocusOut($event)', }, + providers: [{provide: COMBOBOX, useExisting: Combobox}], }) -export class Combobox extends DeferredContentAware implements OnInit { - private readonly _renderer = inject(Renderer2); +export class Combobox { + /** A signal wrapper for directionality. */ + protected textDirection = inject(Directionality).valueSignal.asReadonly(); /** The element that the combobox is attached to. */ - private readonly _elementRef = inject>(ElementRef); + private readonly _elementRef = inject(ElementRef); + + /** A reference to the combobox element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** The DeferredContentAware host directive. */ + private readonly _deferredContentAware = inject(DeferredContentAware, {optional: true}); - /** A reference to the input element. */ - readonly element = this._elementRef.nativeElement; + /** The combobox popup. */ + readonly popup = contentChild>(ComboboxPopup); - /** The popup associated with the combobox. */ - readonly _popup = signal(undefined); + /** + * The filter mode for the combobox. + * - `manual`: The consumer is responsible for filtering the options. + * - `auto-select`: The combobox automatically selects the first matching option. + * - `highlight`: The combobox highlights matching text in the options without changing selection. + */ + filterMode = input<'manual' | 'auto-select' | 'highlight'>('manual'); /** Whether the combobox is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** Whether the combobox is readonly. */ + /** Whether the combobox is read-only. */ readonly readonly = input(false, {transform: booleanAttribute}); - /** Whether the combobox is soft disabled (remains focusable). */ - readonly softDisabled = input(true, {transform: booleanAttribute}); - - /** Whether the combobox should always remain expanded. */ - readonly alwaysExpanded = input(false, {transform: booleanAttribute}); - - /** The tabindex of the combobox. */ - readonly tabIndex = input(undefined, { - alias: 'tabindex', - transform: tabIndexTransform, - }); + /** The value of the first matching item in the popup. */ + readonly firstMatch = input(undefined); /** Whether the combobox is expanded. */ - readonly expanded = model(false); + readonly expanded = computed(() => this.alwaysExpanded() || this._pattern.expanded()); - /** The value of the combobox input. */ - readonly value = model(''); + // TODO: Maybe make expanded a signal that can be passed in? + // Or an "always expanded" option? - /** An inline suggestion to be displayed in the input. */ - readonly inlineSuggestion = input(undefined); + /** Whether the combobox popup should always be expanded, regardless of user interaction. */ + readonly alwaysExpanded = input(false, {transform: booleanAttribute}); + + /** Input element connected to the combobox, if any. */ + readonly inputElement = computed(() => this._pattern.inputs.inputEl()); /** The combobox ui pattern. */ - readonly _pattern = new ComboboxPattern({ + readonly _pattern = new ComboboxPattern({ ...this, - readonly: () => this.readonly(), - element: () => this.element, - expandable: () => true, - popup: computed(() => this._popup()?._pattern), + textDirection: this.textDirection, + disabled: this.disabled, + readonly: this.readonly, + inputValue: signal(''), + inputEl: signal(undefined), + containerEl: () => this._elementRef.nativeElement, + popupControls: () => this.popup()?._controls(), }); constructor() { - super(); - - afterRenderEffect({write: () => this._pattern.keyboardEventRelayEffect()}); afterRenderEffect(() => { - this.contentVisible.set(this._pattern.isExpanded()); + if (this.alwaysExpanded()) { + this._pattern.expanded.set(true); + } }); - if (this._pattern.isEditable()) { - afterRenderEffect(() => { - this._renderer.setProperty(this.element, 'value', this.value()); - }); - afterRenderEffect(() => { - this._pattern.highlightEffect(); - }); - } - } - - ngOnInit() { - if (this.alwaysExpanded()) { - this.expanded.set(true); - } + afterRenderEffect(() => { + if ( + !this._deferredContentAware?.contentVisible() && + (this._pattern.isFocused() || this.alwaysExpanded()) + ) { + this._deferredContentAware?.contentVisible.set(true); + } + }); } - /** Registers a popup with the combobox. */ - _registerPopup(popup: ComboboxPopup) { - this._popup.set(popup); + /** Opens the combobox to the selected item. */ + open() { + this._pattern.open({selected: true}); } - /** Unregisters the popup from the combobox. */ - _unregisterPopup() { - this._popup.set(undefined); + /** Closes the combobox. */ + close() { + this._pattern.close(); } } diff --git a/src/aria/combobox/combobox.zone.spec.ts b/src/aria/combobox/combobox.zone.spec.ts deleted file mode 100644 index ab603ee12fbe..000000000000 --- a/src/aria/combobox/combobox.zone.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component, computed, signal, provideZoneChangeDetection} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; -import {By} from '@angular/platform-browser'; -import {Combobox} from './combobox'; -import {ComboboxPopup} from './combobox-popup'; -import {ComboboxWidget} from './combobox-widget'; -import {Listbox, Option} from '../listbox'; - -describe('Combobox Zone.js integration', () => { - let fixture: ComponentFixture; - let inputElement: HTMLInputElement; - - beforeEach(async () => { - TestBed.configureTestingModule({ - providers: [provideZoneChangeDetection()], - }); - fixture = TestBed.createComponent(ComboboxListboxZoneExample); - await fixture.whenStable(); - const inputDebugElement = fixture.debugElement.query(By.directive(Combobox)); - inputElement = inputDebugElement.nativeElement as HTMLInputElement; - }); - - const focus = () => { - inputElement.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - fixture.detectChanges(); - }; - - const keydown = (key: string) => { - focus(); - inputElement.dispatchEvent( - new KeyboardEvent('keydown', { - key, - bubbles: true, - }), - ); - fixture.detectChanges(); - }; - - function getOption(text: string): HTMLElement | null { - const options = Array.from(document.querySelectorAll('[ngoption]')) as HTMLElement[]; - return options.find(option => option.textContent?.trim() === text) || null; - } - - it('should relay ArrowDown to the listbox and update active descendant', fakeAsync(() => { - // Open the popup (sets active descendant to Alabama via default state) - keydown('ArrowDown'); - tick(); - fixture.detectChanges(); - - // Check if expanded is true - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - // Active descendant is bound to host: - const alabama = getOption('Alabama')!; - expect(inputElement.getAttribute('aria-activedescendant')).toBe(alabama.id); - - // Press ArrowDown again to move to Alaska - keydown('ArrowDown'); - tick(); - fixture.detectChanges(); - - const alaska = getOption('Alaska')!; - expect(inputElement.getAttribute('aria-activedescendant')).toBe(alaska.id); - })); -}); - -@Component({ - template: ` -
      - - -
        - @for (option of options(); track option) { -
      • {{option}}
      • - } -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], -}) -class ComboboxListboxZoneExample { - popupExpanded = signal(false); - searchString = signal(''); - value = signal([]); - options = computed(() => ['Alabama', 'Alaska', 'Arizona', 'Arkansas']); -} diff --git a/src/aria/combobox/public-api.ts b/src/aria/combobox/public-api.ts index 997e18d45952..b8ea7ceefa50 100644 --- a/src/aria/combobox/public-api.ts +++ b/src/aria/combobox/public-api.ts @@ -7,8 +7,10 @@ */ export {Combobox} from './combobox'; +export {ComboboxDialog} from './combobox-dialog'; +export {ComboboxInput} from './combobox-input'; export {ComboboxPopup} from './combobox-popup'; -export {ComboboxWidget} from './combobox-widget'; +export {ComboboxPopupContainer} from './combobox-popup-container'; // This needs to be re-exported, because it's used by the combobox components. // See: https://github.com/angular/components/issues/30663. diff --git a/src/aria/combobox/testing/BUILD.bazel b/src/aria/combobox/testing/BUILD.bazel deleted file mode 100644 index b1644d156a44..000000000000 --- a/src/aria/combobox/testing/BUILD.bazel +++ /dev/null @@ -1,43 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//src/aria/combobox", - "//src/aria/listbox", - "//src/aria/listbox/testing", - "//src/cdk/overlay", - "//src/cdk/testing", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [ - ":unit_tests_lib", - ], -) diff --git a/src/aria/combobox/testing/combobox-harness-filters.ts b/src/aria/combobox/testing/combobox-harness-filters.ts deleted file mode 100644 index 22013f276d3d..000000000000 --- a/src/aria/combobox/testing/combobox-harness-filters.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** A set of criteria that can be used to filter a list of `ComboboxHarness` instances. */ -export interface ComboboxHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose placeholder matches the given value. */ - placeholder?: string | RegExp; - /** Only find instances whose value matches the given value. */ - value?: string | RegExp; - /** Only find instances with the given disabled state. */ - disabled?: boolean; -} diff --git a/src/aria/combobox/testing/combobox-harness.spec.ts b/src/aria/combobox/testing/combobox-harness.spec.ts deleted file mode 100644 index 8b4ae5351d1a..000000000000 --- a/src/aria/combobox/testing/combobox-harness.spec.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component, signal} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Combobox, ComboboxPopup, ComboboxWidget} from '../index'; -import {Listbox, Option} from '../../listbox'; -import {ListboxHarness, ListboxOptionHarness} from '../../listbox/testing/listbox-harness'; -import {ComboboxHarness} from './combobox-harness'; -import {OverlayModule} from '@angular/cdk/overlay'; - -describe('ComboboxHarness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - function setupTest(component: any) { - fixture = TestBed.createComponent(component); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - } - - describe('Basic usage', () => { - beforeEach(() => setupTest(ComboboxTestApp)); - - it('should load combobox harness', async () => { - await expectAsync(loader.getHarness(ComboboxHarness)).toBeResolved(); - }); - - it('should get and set values', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - await combobox.setValue('California'); - fixture.detectChanges(); - - expect(await combobox.getValue()).toBe('California'); - }); - - it('should correctly report disabled state', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - expect(await combobox.isDisabled()).toBeFalse(); - - fixture.componentInstance.disabled.set(true); - fixture.detectChanges(); - - expect(await combobox.isDisabled()).toBeTrue(); - }); - - it('should open and close the popup', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - expect(await combobox.isOpen()).toBeFalse(); - - await combobox.open(); - fixture.detectChanges(); - expect(await combobox.isOpen()).toBeTrue(); - - await combobox.close(); - fixture.detectChanges(); - expect(await combobox.isOpen()).toBeFalse(); - }); - - it('should allow loading nested harnesses within the popup content via unified container API', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - await combobox.open(); - fixture.detectChanges(); - - // We access the main widget harness using getPopupWidget. - const listbox = await combobox.getPopupWidget(ListboxHarness); - const options = await listbox.getOptions(); - expect(options.length).toBe(3); - }); - - it('should fail to resolve nested items when closed', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - // Popup isn't open yet, so getPopupWidget should fail. - await expectAsync(combobox.getPopupWidget(ListboxHarness)).toBeRejectedWithError( - /Cannot retrieve popup content because the combobox is closed/, - ); - }); - - it('should support getting explicit popup loader for descendant matching', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - await combobox.open(); - fixture.detectChanges(); - - const popupLoader = await combobox.getPopupLoader(); - // We are testing that the loader works for finding actual children (Options). - const option = await popupLoader.getHarness(ListboxOptionHarness); - expect(option).toBeDefined(); - }); - - it('should support focusing and blurring', async () => { - const combobox = await loader.getHarness(ComboboxHarness); - await combobox.focus(); - expect(await combobox.isFocused()).toBeTrue(); - - await combobox.blur(); - expect(await combobox.isFocused()).toBeFalse(); - }); - }); - - describe('Overlay and Popover integrations', () => { - it('should find and resolve harnesses nested inside standard CdkOverlay', async () => { - setupTest(ComboboxOverlayTestApp); - const combobox = await loader.getHarness(ComboboxHarness); - - await combobox.open(); - fixture.detectChanges(); - - // Should find listbox inside the dynamically attached cdk overlay root container - const listbox = await combobox.getPopupWidget(ListboxHarness); - expect(listbox).toBeDefined(); - expect((await listbox.getOptions()).length).toBe(2); - }); - - it('should resolve nested harnesses when using Native Popover API', async () => { - setupTest(ComboboxNativePopoverTestApp); - const combobox = await loader.getHarness(ComboboxHarness); - - await combobox.open(); - fixture.detectChanges(); - - const listbox = await combobox.getPopupWidget(ListboxHarness); - expect(listbox).toBeDefined(); - expect((await listbox.getOptions()).length).toBe(2); - }); - }); -}); - -@Component({ - template: ` -
      - - - -
      -
      California
      -
      Washington
      -
      Oregon
      -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option], -}) -class ComboboxTestApp { - disabled = signal(false); -} - -@Component({ - template: ` -
      - -
      - - - -
      -
      A
      -
      B
      -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], -}) -class ComboboxOverlayTestApp { - popupExpanded = signal(false); -} - -@Component({ - template: ` -
      - -
      - - - -
      -
      A
      -
      B
      -
      -
      -
      - `, - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], -}) -class ComboboxNativePopoverTestApp { - popupExpanded = signal(false); -} diff --git a/src/aria/combobox/testing/combobox-harness.ts b/src/aria/combobox/testing/combobox-harness.ts deleted file mode 100644 index c13a53b69634..000000000000 --- a/src/aria/combobox/testing/combobox-harness.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - ComponentHarness, - ComponentHarnessConstructor, - ContentContainerComponentHarness, - HarnessLoader, - HarnessPredicate, - TestKey, -} from '@angular/cdk/testing'; -import {ComboboxHarnessFilters} from './combobox-harness-filters'; - -/** Harness for interacting with a standard `ngCombobox` input element in tests. */ -export class ComboboxHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngCombobox]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a combobox with specific attributes. - * @param options Options for filtering which combobox instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: ComboboxHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(ComboboxHarness, options) - .addOption('placeholder', options.placeholder, async (harness, placeholder) => - HarnessPredicate.stringMatches(await harness.getPlaceholder(), placeholder), - ) - .addOption('value', options.value, async (harness, value) => - HarnessPredicate.stringMatches(await harness.getValue(), value), - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** - * Gets the component harness for the active widget contained inside the popup. - * Use this when you need to access the harness of the widget itself (e.g., `ListboxHarness`), - * rather than querying items within it. - * @param type The harness type to locate. Must implement standard static `.with()` method. - */ - async getPopupWidget( - type: ComponentHarnessConstructor & { - with: (options?: {selector?: string}) => HarnessPredicate; - }, - ): Promise { - const host = await this.host(); - const controlsId = await host.getAttribute('aria-controls'); - if (!controlsId) { - throw new Error( - 'Cannot retrieve popup content because the combobox is closed or not associated with a popup controls ID.', - ); - } - return this.documentRootLocatorFactory().locatorFor(type.with({selector: `#${controlsId}`}))(); - } - - /** - * Gets a harness loader scoped to the content inside the popup container. - * Note that lookups performed by this loader will only find descendants of the popup container. - */ - async getPopupLoader(): Promise { - return this.getRootHarnessLoader(); - } - - /** Overrides root loader to automatically resolve queries nested inside the associated popup. */ - protected override async getRootHarnessLoader(): Promise { - const host = await this.host(); - const controlsId = await host.getAttribute('aria-controls'); - if (!controlsId) { - throw new Error( - 'Cannot retrieve popup content because the combobox is closed or not associated with a popup controls ID.', - ); - } - const documentRoot = await this.documentRootLocatorFactory().rootHarnessLoader(); - // Locate the widget by ID, which was assigned by ngComboboxWidget and linked via aria-controls. - return documentRoot.getChildLoader(`#${controlsId}`); - } - - /** Whether the combobox is expanded (popup is open). */ - async isOpen(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-expanded')) === 'true'; - } - - /** Whether the combobox is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets the current value string of the combobox input. */ - async getValue(): Promise { - const host = await this.host(); - return host.getProperty('value'); - } - - /** Sets the value of the combobox input. */ - async setValue(value: string): Promise { - const host = await this.host(); - await host.clear(); - if (value) { - await host.sendKeys(value); - } - } - - /** Gets the placeholder text of the combobox. */ - async getPlaceholder(): Promise { - const host = await this.host(); - return host.getAttribute('placeholder'); - } - - /** Opens the combobox popup if it is currently closed. */ - async open(): Promise { - if (!(await this.isOpen())) { - const host = await this.host(); - await host.focus(); - await host.sendKeys(TestKey.DOWN_ARROW); - } - } - - /** Closes the combobox popup if it is currently open. */ - async close(): Promise { - if (await this.isOpen()) { - const host = await this.host(); - await host.focus(); - await host.sendKeys(TestKey.ESCAPE); - } - } - - /** Focuses the combobox. */ - async focus(): Promise { - return (await this.host()).focus(); - } - - /** Blurs the combobox. */ - async blur(): Promise { - return (await this.host()).blur(); - } - - /** Whether the combobox has focus. */ - async isFocused(): Promise { - return (await this.host()).isFocused(); - } -} diff --git a/src/aria/combobox/testing/index.ts b/src/aria/combobox/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/combobox/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/combobox/testing/public-api.ts b/src/aria/combobox/testing/public-api.ts deleted file mode 100644 index 46b2e5953146..000000000000 --- a/src/aria/combobox/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './combobox-harness'; -export * from './combobox-harness-filters'; diff --git a/src/aria/config.bzl b/src/aria/config.bzl index 8ee99e0c6807..291412b5a3fb 100644 --- a/src/aria/config.bzl +++ b/src/aria/config.bzl @@ -1,21 +1,13 @@ # List of all entry-points of the Angular Aria package. ARIA_ENTRYPOINTS = [ "accordion", - "accordion/testing", + "combobox", "grid", - "grid/testing", "listbox", - "listbox/testing", "menu", - "menu/testing", - "combobox", - "combobox/testing", "tabs", - "tabs/testing", "toolbar", - "toolbar/testing", "tree", - "tree/testing", "private", ] diff --git a/src/aria/grid/BUILD.bazel b/src/aria/grid/BUILD.bazel index ec62fa5648f1..c03e388e1e9e 100644 --- a/src/aria/grid/BUILD.bazel +++ b/src/aria/grid/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "extract_api_to_json", "ng_project", "ng_web_test_suite") +load("//tools:defaults.bzl", "extract_api_to_json", "ng_project") package(default_visibility = ["//visibility:public"]) @@ -16,28 +16,6 @@ ng_project( ], ) -ng_project( - name = "unit_test_sources", - testonly = True, - srcs = glob( - ["**/*.spec.ts"], - exclude = ["**/*.e2e.spec.ts"], - ), - deps = [ - ":grid", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//:node_modules/axe-core", - "//src/aria/private/testing", - "//src/cdk/testing/private", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_test_sources"], -) - filegroup( name = "source-files", srcs = glob( diff --git a/src/aria/grid/grid-cell-widget.ts b/src/aria/grid/grid-cell-widget.ts index 5817214d4cc6..f4402f8fbd50 100644 --- a/src/aria/grid/grid-cell-widget.ts +++ b/src/aria/grid/grid-cell-widget.ts @@ -18,7 +18,7 @@ import { output, Signal, } from '@angular/core'; -import {GridCellWidgetPattern, ElementResolver} from '../private'; +import {GridCellWidgetPattern} from '../private'; import {GRID_CELL} from './grid-tokens'; /** @@ -35,6 +35,8 @@ import {GRID_CELL} from './grid-tokens'; * * ``` * + * @developerPreview 21.0 + * * @see [Grid](guide/aria/grid) */ @Directive({ @@ -44,7 +46,6 @@ import {GRID_CELL} from './grid-tokens'; '[attr.data-active]': 'active()', '[attr.data-active-control]': 'isActivated() ? "widget" : "cell"', '[tabindex]': '_tabIndex()', - '[attr.id]': 'id()', }, }) export class GridCellWidget { @@ -70,7 +71,7 @@ export class GridCellWidget { readonly disabled = input(false, {transform: booleanAttribute}); /** The target that will receive focus instead of the widget. */ - readonly focusTarget = input>(); + readonly focusTarget = input(); /** Emits when the widget is activated. */ readonly activated = output(); @@ -86,7 +87,7 @@ export class GridCellWidget { * If a focus target exists then return -1. Unless an override. */ protected readonly _tabIndex: Signal = computed( - () => this.tabindex() ?? this._pattern.tabIndex(), + () => this.tabindex() ?? (this.focusTarget() ? -1 : this._pattern.tabIndex()), ); /** The UI pattern for the grid cell widget. */ @@ -94,8 +95,10 @@ export class GridCellWidget { ...this, element: () => this.element, cell: () => this._cell._pattern, - onActivate: e => this.activated.emit(e), - onDeactivate: e => this.deactivated.emit(e), + focusTarget: computed(() => { + const target = this.focusTarget(); + return target instanceof ElementRef ? target.nativeElement : target; + }), }); /** Whether the widget is activated. */ @@ -104,12 +107,18 @@ export class GridCellWidget { } constructor() { - afterRenderEffect({ - write: () => this._pattern.activationEffect(), + afterRenderEffect(() => { + const activateEvent = this._pattern.lastActivateEvent(); + if (activateEvent) { + this.activated.emit(activateEvent); + } }); - afterRenderEffect({ - write: () => this._pattern.deactivationEffect(), + afterRenderEffect(() => { + const deactivateEvent = this._pattern.lastDeactivateEvent(); + if (deactivateEvent) { + this.deactivated.emit(deactivateEvent); + } }); } diff --git a/src/aria/grid/grid-cell.ts b/src/aria/grid/grid-cell.ts index 4b19ada21b2c..16820bd35940 100644 --- a/src/aria/grid/grid-cell.ts +++ b/src/aria/grid/grid-cell.ts @@ -11,19 +11,17 @@ import { afterRenderEffect, booleanAttribute, computed, - contentChild, + contentChildren, Directive, ElementRef, inject, input, model, - OnDestroy, - OnInit, Signal, Renderer2, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; -import {GridCellPattern, GridCellWidgetPattern} from '../private'; +import {GridCellPattern} from '../private'; import {GridCellWidget} from './grid-cell-widget'; import {GRID_CELL, GRID_ROW} from './grid-tokens'; @@ -38,6 +36,8 @@ import {GRID_CELL, GRID_ROW} from './grid-tokens'; * * ``` * + * @developerPreview 21.0 + * * @see [Grid](guide/aria/grid) */ @Directive({ @@ -45,7 +45,7 @@ import {GRID_CELL, GRID_ROW} from './grid-tokens'; exportAs: 'ngGridCell', providers: [{provide: GRID_CELL, useExisting: GridCell}], }) -export class GridCell implements OnInit, OnDestroy { +export class GridCell { private readonly _elementRef = inject(ElementRef); private readonly _renderer = inject(Renderer2); @@ -55,12 +55,12 @@ export class GridCell implements OnInit, OnDestroy { /** Whether the cell is currently active (focused). */ readonly active = computed(() => this._pattern.active()); - /** The widget contained within this cell, if any. */ - private readonly _widget = contentChild(GridCellWidget, {descendants: true}); + /** The widgets contained within this cell, if any. */ + private readonly _widgets = contentChildren(GridCellWidget, {descendants: true}); /** The UI pattern for the widget in this cell. */ - private readonly _widgetPattern: Signal = computed( - () => this._widget()?._pattern, + private readonly _widgetPatterns: Signal = computed(() => + this._widgets().map(w => w._pattern), ); /** The parent row. */ @@ -96,6 +96,12 @@ export class GridCell implements OnInit, OnDestroy { /** Whether the cell is selectable. */ readonly selectable = input(true); + /** Orientation of the widgets in the cell. */ + readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); + + /** Whether widgets navigation wraps. */ + readonly wrap = input(true, {transform: booleanAttribute}); + /** The tabindex override. */ readonly tabindex = input(); @@ -112,7 +118,7 @@ export class GridCell implements OnInit, OnDestroy { ...this, grid: this._row._gridPattern, row: () => this._row._pattern, - widget: this._widgetPattern, + widgets: this._widgetPatterns, getWidget: e => this._getWidget(e), element: () => this.element, }); @@ -129,7 +135,7 @@ export class GridCell implements OnInit, OnDestroy { toggle('role', this.role()); toggle('id', pattern.id()); toggle('rowspan', rowSpan); - toggle('colspan', colSpan); + toggle('colspan', rowSpan); toggle('aria-rowspan', rowSpan); toggle('aria-colspan', colSpan); toggle('data-active', this.active()); @@ -143,14 +149,6 @@ export class GridCell implements OnInit, OnDestroy { }); } - ngOnInit() { - this._row._collection.register(this); - } - - ngOnDestroy() { - this._row._collection.unregister(this); - } - private _toggleAttribute = (name: string, value: unknown) => { if (value == null) { this._renderer.removeAttribute(this.element, name); @@ -162,13 +160,11 @@ export class GridCell implements OnInit, OnDestroy { /** Gets the cell widget pattern for a given element. */ private _getWidget(element: Element | null | undefined): any | undefined { let target = element; - const widget = this._widgetPattern(); - - if (!widget) return undefined; while (target) { - if (widget.element() === target) { - return widget; + const pattern = this._widgetPatterns().find(w => w.element() === target); + if (pattern) { + return pattern; } target = target.parentElement?.closest('[ngGridCellWidget]'); diff --git a/src/aria/grid/grid-row.ts b/src/aria/grid/grid-row.ts index 7a15b04ce009..7a24a79f6dea 100644 --- a/src/aria/grid/grid-row.ts +++ b/src/aria/grid/grid-row.ts @@ -7,19 +7,17 @@ */ import { - afterNextRender, computed, + contentChildren, Directive, ElementRef, inject, input, - OnDestroy, - OnInit, Signal, } from '@angular/core'; -import {GridPattern, GridRowPattern, SortedCollection} from '../private'; -import {GRID_ROW, GRID} from './grid-tokens'; -import {GridCell} from './grid-cell'; +import {GridPattern, GridRowPattern} from '../private'; +import {Grid} from './grid'; +import {GRID_CELL, GRID_ROW} from './grid-tokens'; /** * Represents a row within a grid. It is a container for `ngGridCell` directives. @@ -30,6 +28,8 @@ import {GridCell} from './grid-cell'; * * ``` * + * @developerPreview 21.0 + * * @see [Grid](guide/aria/grid) */ @Directive({ @@ -41,23 +41,23 @@ import {GridCell} from './grid-cell'; }, providers: [{provide: GRID_ROW, useExisting: GridRow}], }) -export class GridRow implements OnInit, OnDestroy { +export class GridRow { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of cells in this row. */ - readonly _collection = new SortedCollection(); + /** The cells that make up this row. */ + private readonly _cells = contentChildren(GRID_CELL, {descendants: true}); /** The UI patterns for the cells in this row. */ private readonly _cellPatterns: Signal = computed(() => - this._collection.orderedItems().map(c => c._pattern), + this._cells().map(c => c._pattern), ); /** The parent grid. */ - private readonly _grid = inject(GRID); + private readonly _grid = inject(Grid); /** The parent grid UI pattern. */ readonly _gridPattern = computed(() => this._grid._pattern); @@ -71,19 +71,4 @@ export class GridRow implements OnInit, OnDestroy { cells: this._cellPatterns, grid: this._gridPattern, }); - - constructor() { - afterNextRender(() => { - this._collection.startObserving(this.element); - }); - } - - ngOnInit() { - this._grid._collection.register(this); - } - - ngOnDestroy() { - this._grid._collection.unregister(this); - this._collection.stopObserving(); - } } diff --git a/src/aria/grid/grid-tokens.ts b/src/aria/grid/grid-tokens.ts index 2f314b114f55..3e70c18a6f79 100644 --- a/src/aria/grid/grid-tokens.ts +++ b/src/aria/grid/grid-tokens.ts @@ -9,13 +9,9 @@ import {InjectionToken} from '@angular/core'; import type {GridCell} from './grid-cell'; import type {GridRow} from './grid-row'; -import type {Grid} from './grid'; /** Token used to expose a `GridCell`. */ export const GRID_CELL = new InjectionToken('GRID_CELL'); /** Token used to expose a `GridRow`. */ export const GRID_ROW = new InjectionToken('GRID_ROW'); - -/** Token used to expose a `Grid`. */ -export const GRID = new InjectionToken('GRID'); diff --git a/src/aria/grid/grid.spec.ts b/src/aria/grid/grid.spec.ts deleted file mode 100644 index 3e799a4670eb..000000000000 --- a/src/aria/grid/grid.spec.ts +++ /dev/null @@ -1,1173 +0,0 @@ -import {Component, DebugElement, signal, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {By} from '@angular/platform-browser'; -import {Grid} from './grid'; -import {GridRow} from './grid-row'; -import {GridCell} from './grid-cell'; -import {GridCellWidget} from './grid-cell-widget'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; - -interface ModifierKeys { - ctrlKey?: boolean; - shiftKey?: boolean; - altKey?: boolean; - metaKey?: boolean; -} - -interface WidgetConfig { - id?: string; - type?: 'simple' | 'complex' | 'editable'; - disabled?: boolean; - tabindex?: number; - hasTarget?: boolean; -} - -interface CellConfig { - id: string; - disabled?: boolean; - role?: 'gridcell' | 'columnheader' | 'rowheader'; - rowIndex?: number; - colIndex?: number; - rowSpan?: number; - colSpan?: number; - selectable?: boolean; - widgets?: WidgetConfig[]; -} - -interface RowConfig { - rowIndex?: number; - cells: CellConfig[]; -} - -function createGridData(): RowConfig[] { - return Array.from({length: 3}, (_, rIndex) => ({ - cells: Array.from({length: 3}, (_, cIndex) => ({ - id: `c${rIndex}-${cIndex}`, - })), - })); -} - -describe('Grid directives', () => { - let fixture: ComponentFixture; - let gridDebugElement: DebugElement; - let gridElement: HTMLElement; - let gridInstance: Grid; - - const keydown = async (key: string, modifierKeys: ModifierKeys = {}) => { - const event = new KeyboardEvent('keydown', {key, bubbles: true, ...modifierKeys}); - gridElement.dispatchEvent(event); - await fixture.whenStable(); - }; - - const pointerDown = async (target: HTMLElement, eventInit: PointerEventInit = {}) => { - target.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); - }; - - const up = async (modifierKeys?: ModifierKeys) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: ModifierKeys) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: ModifierKeys) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: ModifierKeys) => await keydown('ArrowRight', modifierKeys); - const home = async (modifierKeys?: ModifierKeys) => await keydown('Home', modifierKeys); - const end = async (modifierKeys?: ModifierKeys) => await keydown('End', modifierKeys); - const enter = async (modifierKeys?: ModifierKeys) => await keydown('Enter', modifierKeys); - const space = async (modifierKeys?: ModifierKeys) => await keydown(' ', modifierKeys); - - const tabIntoGrid = async () => { - const focusableElement = gridElement.querySelector('[tabindex="0"]') as HTMLElement; - focusableElement?.focus(); - await fixture.whenStable(); - }; - - function getActiveCellId(): string | null { - if (fixture.componentInstance.focusMode() === 'roving') { - const activeEl = gridElement.querySelector('[tabindex="0"]'); - return activeEl ? activeEl.id : null; - } - return gridElement.getAttribute('aria-activedescendant'); - } - - async function setupGrid(opts?: { - disabled?: boolean; - multi?: boolean; - rowWrap?: 'continuous' | 'loop' | 'nowrap'; - colWrap?: 'continuous' | 'loop' | 'nowrap'; - focusMode?: 'roving' | 'activedescendant'; - softDisabled?: boolean; - enableSelection?: boolean; - selectionMode?: 'follow' | 'explicit'; - gridData?: RowConfig[]; - tabIndex?: number; - }) { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({}); - fixture = TestBed.createComponent(GridTestComponent); - const testComponent = fixture.componentInstance; - - if (opts?.disabled !== undefined) testComponent.disabled.set(opts.disabled); - if (opts?.multi !== undefined) testComponent.multi.set(opts.multi); - if (opts?.rowWrap !== undefined) testComponent.rowWrap.set(opts.rowWrap); - if (opts?.colWrap !== undefined) testComponent.colWrap.set(opts.colWrap); - if (opts?.focusMode !== undefined) testComponent.focusMode.set(opts.focusMode); - if (opts?.softDisabled !== undefined) testComponent.softDisabled.set(opts.softDisabled); - if (opts?.enableSelection !== undefined) - testComponent.enableSelection.set(opts.enableSelection); - if (opts?.selectionMode !== undefined) testComponent.selectionMode.set(opts.selectionMode); - if (opts?.tabIndex !== undefined) testComponent.tabIndex.set(opts.tabIndex); - - if (opts?.gridData !== undefined) { - testComponent.gridData.set(opts.gridData); - } else { - testComponent.gridData.set(createGridData()); - } - - await fixture.whenStable(); - gridDebugElement = fixture.debugElement.query(By.directive(Grid)); - gridElement = gridDebugElement.nativeElement; - gridInstance = gridDebugElement.injector.get(Grid); - } - - describe('Grid', () => { - describe('ARIA attributes and roles', () => { - it('should set role="grid" on the host element', async () => { - await setupGrid(); - expect(gridElement.getAttribute('role')).toBe('grid'); - }); - - it('should set aria-disabled="true" when the disabled input is true', async () => { - await setupGrid({disabled: true}); - expect(gridElement.getAttribute('aria-disabled')).toBe('true'); - }); - - it('should set aria-disabled="false" when the disabled input is false', async () => { - await setupGrid({disabled: false}); - expect(gridElement.getAttribute('aria-disabled')).toBe('false'); - }); - - it('should set aria-multiselectable="true" when the multi input is true', async () => { - await setupGrid({enableSelection: true, multi: true}); - expect(gridElement.getAttribute('aria-multiselectable')).toBe('true'); - }); - - it('should set aria-multiselectable="false" when the multi input is false', async () => { - await setupGrid({enableSelection: true, multi: false}); - expect(gridElement.getAttribute('aria-multiselectable')).toBe('false'); - }); - - it('should set aria-activedescendant to the active cell id', async () => { - await setupGrid({focusMode: 'activedescendant'}); - // Simulate gaining focus so active Descendant gets populated - gridElement.dispatchEvent(new Event('focusin')); - await fixture.whenStable(); - expect(gridElement.getAttribute('aria-activedescendant')).toBe('c0-0'); - }); - }); - - describe('focus management', () => { - it('should set tabindex based on the pattern tabIndex', async () => { - await setupGrid({focusMode: 'roving'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - await fixture.whenStable(); - expect(gridElement.getAttribute('tabindex')).toBe('-1'); // roving defaults to -1 on host - - await setupGrid({focusMode: 'activedescendant'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - await fixture.whenStable(); - expect(gridElement.getAttribute('tabindex')).toBe('0'); // activedescendant defaults to 0 on host - }); - - it('should be able to override tabindex', async () => { - await setupGrid({focusMode: 'activedescendant', tabIndex: -1}); - expect(gridElement.getAttribute('tabindex')).toBe('-1'); - }); - - it('should activate the cell when the grid receives focusin', async () => { - await setupGrid(); - - // Let effect run to set default state which sets initial active cell - gridInstance._pattern.setDefaultStateEffect(); - - const cell1 = fixture.debugElement.query(By.directive(GridCell)).nativeElement; - - // Dispatch focusin to the cell - cell1.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - expect(gridInstance._pattern.activeCell()?.element()).toBe(cell1); - expect(gridInstance._pattern.isFocused()).toBeTrue(); - }); - - it('should deactivate the grid when focusout moves outside the grid', async () => { - await setupGrid(); - const cell1 = fixture.debugElement.query(By.directive(GridCell)).nativeElement; - - // Focus first - - gridInstance._pattern.setDefaultStateEffect(); - cell1.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - expect(gridInstance._pattern.isFocused()).toBeTrue(); - - // Focusout (blur) - // Add relatedTarget so we simulate moving focus out completely, otherwise the target doesn't update correctly - const focusOutEvent = new FocusEvent('focusout', { - bubbles: true, - relatedTarget: document.body, - }); - cell1.dispatchEvent(focusOutEvent); - await fixture.whenStable(); - - expect(gridInstance._pattern.isFocused()).toBeFalse(); - }); - }); - - describe('keyboard interactions', () => { - describe('navigation keys', () => { - beforeEach(async () => { - await setupGrid(); - // Let effect run to set default state which sets initial active cell - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - // Start interactions from the middle cell (c1-1) - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - centerCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - }); - - it('should move focus up to the previous row on ArrowUp', async () => { - await up(); - - expect(getActiveCellId()).toBe('c0-1'); - }); - - it('should move focus down to the next row on ArrowDown', async () => { - await down(); - - expect(getActiveCellId()).toBe('c2-1'); - }); - - it('should move focus left to the previous column on ArrowLeft', async () => { - await left(); - - expect(getActiveCellId()).toBe('c1-0'); - }); - - it('should move focus right to the next column on ArrowRight', async () => { - await right(); - - expect(getActiveCellId()).toBe('c1-2'); - }); - - it('should move focus to the first cell in the row on Home', async () => { - await home(); - - expect(getActiveCellId()).toBe('c1-0'); - }); - - it('should move focus to the last cell in the row on End', async () => { - await end(); - - expect(getActiveCellId()).toBe('c1-2'); - }); - - describe('colWrap configuration', () => { - it('should wrap to next row when moving right from last column of a row', async () => { - await setupGrid({colWrap: 'continuous'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-2') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await right(); - expect(getActiveCellId()).toBe('c2-0'); - }); - - it('should wrap to previous row when moving left from first column of a row', async () => { - await setupGrid({colWrap: 'continuous'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-0') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await left(); - expect(getActiveCellId()).toBe('c0-2'); - }); - - it('should not wrap to next row when passing row boundaries if colWrap is nowrap', async () => { - await setupGrid({colWrap: 'nowrap'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-2') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await right(); - expect(getActiveCellId()).toBe('c1-2'); - }); - - it('should wrap around the same row when passing row boundaries if colWrap is loop', async () => { - await setupGrid({colWrap: 'loop'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-2') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await right(); - expect(getActiveCellId()).toBe('c1-0'); - }); - }); - - describe('rowWrap configuration', () => { - it('should wrap to next column when moving down from last row of a column', async () => { - await setupGrid({rowWrap: 'continuous'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c2-1') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await down(); - expect(getActiveCellId()).toBe('c0-2'); - }); - - it('should wrap to previous column when moving up from first row of a column', async () => { - await setupGrid({rowWrap: 'continuous'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c0-1') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await up(); - expect(getActiveCellId()).toBe('c2-0'); - }); - - it('should not wrap to next column when passing column boundaries if rowWrap is nowrap', async () => { - await setupGrid({rowWrap: 'nowrap'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c2-1') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await down(); - expect(getActiveCellId()).toBe('c2-1'); - }); - - it('should wrap around the same column when passing column boundaries if rowWrap is loop', async () => { - await setupGrid({rowWrap: 'loop'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c2-1') as HTMLElement; - cell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - await down(); - expect(getActiveCellId()).toBe('c0-1'); - }); - }); - }); - - describe('selection keys', () => { - describe('selectionMode="explicit"', () => { - beforeEach(async () => { - await setupGrid({ - enableSelection: true, - selectionMode: 'explicit', - multi: true, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - // Start interactions from the middle cell - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - centerCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - }); - - it('should toggle selection of the active cell on Space', async () => { - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - - await space(); - - expect(centerCell.getAttribute('aria-selected')).toBe('true'); - - await space(); - - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should trigger default action of the active cell on Enter', async () => { - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - - await enter(); - - expect(centerCell.getAttribute('aria-selected')).toBe('true'); - - await enter(); - - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should select all selectable cells on Ctrl+A', async () => { - await keydown('a', {ctrlKey: true}); - - const cells = gridElement.querySelectorAll('[ngGridCell]'); - for (let i = 0; i < cells.length; i++) { - expect(cells[i].getAttribute('aria-selected')).toBe('true'); - } - - await keydown('a', {ctrlKey: true}); - - for (let i = 0; i < cells.length; i++) { - expect(cells[i].getAttribute('aria-selected')).toBe('false'); - } - }); - }); - - describe('selectionMode="follow"', () => { - beforeEach(async () => { - await setupGrid({ - enableSelection: true, - selectionMode: 'follow', - multi: false, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - // Start interactions from the middle cell - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - centerCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - }); - - it('should select an item when navigated to with Arrow keys and deselect others', async () => { - const centerCell = gridElement.querySelector('#c1-1') as HTMLElement; - const topCell = gridElement.querySelector('#c0-1') as HTMLElement; - - // Focus does not select by itself - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - expect(topCell.getAttribute('aria-selected')).toBe('false'); - - await up(); - - // Arrow keys select the target - expect(centerCell.getAttribute('aria-selected')).toBe('false'); - expect(topCell.getAttribute('aria-selected')).toBe('true'); - - await down(); - - expect(topCell.getAttribute('aria-selected')).toBe('false'); - expect(centerCell.getAttribute('aria-selected')).toBe('true'); - }); - }); - }); - }); - - describe('click interactions', () => { - beforeEach(async () => { - await setupGrid({ - enableSelection: true, - selectionMode: 'explicit', - multi: true, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - }); - - it('should focus and select the clicked cell on click', async () => { - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('aria-selected')).toBe('false'); - - await pointerDown(cell); - - expect(cell.getAttribute('aria-selected')).toBe('true'); - expect(getActiveCellId()).toBe('c1-1'); - }); - }); - - describe('configuration', () => { - it('should prevent multiple selections if selectionMode is single (multi false)', async () => { - await setupGrid({ - enableSelection: true, - selectionMode: 'explicit', - multi: false, - }); - - const cell1 = gridElement.querySelector('#c0-0') as HTMLElement; - const cell2 = gridElement.querySelector('#c1-1') as HTMLElement; - - await pointerDown(cell1); - expect(cell1.getAttribute('aria-selected')).toBe('true'); - - await pointerDown(cell2); - expect(cell1.getAttribute('aria-selected')).toBe('false'); - expect(cell2.getAttribute('aria-selected')).toBe('true'); - }); - - it('should allow interaction but indicate disabled state if softDisabled is true', async () => { - const gridData = createGridData(); - gridData[1].cells[1].disabled = true; - await setupGrid({ - enableSelection: true, - selectionMode: 'explicit', - softDisabled: true, - gridData, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const disabledCell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(disabledCell.getAttribute('aria-disabled')).toBe('true'); - - // Can still interact visually, e.g. focus - disabledCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - expect(getActiveCellId()).toBe('c1-1'); - - // But space should not select it? Wait, interaction is allowed, but actions might be blocked. - await space(); - expect(disabledCell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should skip disabled cells if softDisabled is false', async () => { - const gridData = createGridData(); - gridData[1].cells[1].disabled = true; - await setupGrid({ - softDisabled: false, - gridData, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const startCell = gridElement.querySelector('#c1-0') as HTMLElement; - startCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - // Navigating right from c1-0 should skip c1-1 and go to c1-2 - await right(); - expect(getActiveCellId()).toBe('c1-2'); - }); - }); - - describe('dynamic updates', () => { - it('should update row order correctly after rows are shuffled', async () => { - await setupGrid(); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const rowPatternsBefore = gridInstance._pattern.inputs.rows(); - expect(rowPatternsBefore.length).toBe(3); - expect(rowPatternsBefore[0].inputs.cells()[0].element()?.id).toBe('c0-0'); - - const gridData = fixture.componentInstance.gridData(); - const firstRow = gridData.shift()!; - gridData.push(firstRow); - fixture.componentInstance.gridData.set([...gridData]); - await fixture.whenStable(); - await waitForMicrotasks(); - - const rowPatternsAfter = gridInstance._pattern.inputs.rows(); - expect(rowPatternsAfter.length).toBe(3); - expect(rowPatternsAfter[0].inputs.cells()[0].element()?.id).toBe('c1-0'); - }); - }); - }); - - describe('GridRow', () => { - describe('ARIA attributes and roles', () => { - it('should set role="row" on the host element', async () => { - await setupGrid(); - const row = gridElement.querySelector('tr') as HTMLElement; - expect(row.getAttribute('role')).toBe('row'); - }); - - it('should set aria-rowindex based on the rowIndex input', async () => { - const gridData = createGridData(); - gridData[0].rowIndex = 5; - await setupGrid({gridData}); - const row = gridElement.querySelector('tr') as HTMLElement; - expect(row.getAttribute('aria-rowindex')).toBe('5'); - }); - }); - - describe('dynamic updates', () => { - it('should update cell order correctly after cells are shuffled', async () => { - await setupGrid(); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const firstRow = gridDebugElement.query(By.directive(GridRow)).injector.get(GridRow); - const cellPatternsBefore = firstRow._pattern.inputs.cells(); - expect(cellPatternsBefore.length).toBe(3); - expect(cellPatternsBefore[0].element()?.id).toBe('c0-0'); - - const gridData = fixture.componentInstance.gridData(); - const firstRowCells = gridData[0].cells; - const firstCell = firstRowCells.shift()!; - firstRowCells.push(firstCell); - fixture.componentInstance.gridData.set([...gridData]); - await fixture.whenStable(); - await waitForMicrotasks(); - - const cellPatternsAfter = firstRow._pattern.inputs.cells(); - expect(cellPatternsAfter.length).toBe(3); - expect(cellPatternsAfter[0].element()?.id).toBe('c0-1'); - }); - }); - }); - - describe('GridCell', () => { - describe('ARIA attributes and roles', () => { - it('should set the role based on the role input', async () => { - const gridData = createGridData(); - gridData[1].cells[1].role = 'columnheader'; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('role')).toBe('columnheader'); - }); - - it('should set aria-rowindex to the provided row index', async () => { - const gridData = createGridData(); - gridData[1].cells[1].rowIndex = 4; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('aria-rowindex')).toBe('4'); - }); - - it('should set aria-colindex to the provided column index', async () => { - const gridData = createGridData(); - gridData[1].cells[1].colIndex = 3; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('aria-colindex')).toBe('3'); - }); - - it('should set aria-selected="true" when the cell is selected', async () => { - await setupGrid({enableSelection: true, selectionMode: 'explicit'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('aria-selected')).toBe('false'); - - await pointerDown(cell); - - expect(cell.getAttribute('aria-selected')).toBe('true'); - }); - - it('should set aria-selected="false" when the cell is unselected', async () => { - await setupGrid({enableSelection: true, selectionMode: 'explicit'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - await pointerDown(cell); - expect(cell.getAttribute('aria-selected')).toBe('true'); - - await pointerDown(cell); - expect(cell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should set aria-rowspan to the provided rowspan configuration', async () => { - const gridData = createGridData(); - gridData[1].cells[1].rowSpan = 2; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('rowspan')).toBe('2'); - expect(cell.getAttribute('aria-rowspan')).toBe('2'); - }); - - it('should set aria-colspan to the provided colspan configuration', async () => { - const gridData = createGridData(); - gridData[1].cells[1].colSpan = 3; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('colspan')).toBe('3'); - expect(cell.getAttribute('aria-colspan')).toBe('3'); - }); - - it('should set id from the id input if provided', async () => { - const gridData = createGridData(); - gridData[1].cells[1].id = 'custom-id'; - await setupGrid({gridData}); - const cell = gridElement.querySelector('#custom-id') as HTMLElement; - expect(cell.getAttribute('id')).toBe('custom-id'); - }); - }); - - describe('focus management', () => { - it('should set tabindex="0" on the active cell', async () => { - await setupGrid({focusMode: 'roving'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c0-0') as HTMLElement; - expect(cell.getAttribute('tabindex')).toBe('0'); - }); - - it('should set tabindex="-1" on inactive cells', async () => { - await setupGrid({focusMode: 'roving'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - expect(cell.getAttribute('tabindex')).toBe('-1'); - }); - - it('should set data-active="true" when the cell becomes active', async () => { - await setupGrid(); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c0-0') as HTMLElement; - expect(cell.getAttribute('data-active')).toBe('true'); - }); - }); - - describe('configuration', () => { - it('should prevent selection when disabled input is true', async () => { - const gridData = createGridData(); - gridData[1].cells[1].disabled = true; - await setupGrid({enableSelection: true, selectionMode: 'explicit', gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - await pointerDown(cell); - - expect(cell.getAttribute('aria-selected')).toBe('false'); - }); - - it('should prevent interaction when disabled input is true', async () => { - const gridData = createGridData(); - gridData[1].cells[1].disabled = true; - await setupGrid({ - enableSelection: true, - selectionMode: 'explicit', - softDisabled: false, - gridData, - }); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const startCell = gridElement.querySelector('#c1-0') as HTMLElement; - startCell.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - - // Navigating right from c1-0 should skip c1-1 and go to c1-2 because it's completely disabled - await right(); - expect(getActiveCellId()).toBe('c1-2'); - }); - - it('should prevent selection when selectable input is false', async () => { - const gridData = createGridData(); - gridData[1].cells[1].selectable = false; - await setupGrid({enableSelection: true, selectionMode: 'explicit', gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - await pointerDown(cell); - - expect(cell.hasAttribute('aria-selected')).toBeFalse(); - }); - - it('should update the selected property when bound value changes', async () => { - // Needs a two-way binding test, but we can just test selection updates since it is model() - await setupGrid({enableSelection: true, selectionMode: 'explicit'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cellDirective = fixture.debugElement.query(By.css('#c1-1')).injector.get(GridCell); - expect(cellDirective.selected()).toBeFalse(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - await pointerDown(cell); - - expect(cellDirective.selected()).toBeTrue(); - }); - }); - }); - - describe('GridCellWidget', () => { - describe('ARIA and Host attributes', () => { - it('should set data-active="true" when the widget is active', async () => { - const gridData = createGridData(); - gridData[1].cells[1].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - const widget = gridElement.querySelector('#w1') as HTMLElement; - - expect(widget.getAttribute('data-active')).toBe('false'); - await pointerDown(cell); - expect(widget.getAttribute('data-active')).toBe('true'); - }); - - it('should set data-active-control="widget" when the widget is activated', async () => { - const gridData = createGridData(); - gridData[1].cells[1].widgets = [{id: 'w1', type: 'editable'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - const widget = gridElement.querySelector('#w1') as HTMLElement; - - await pointerDown(cell); - expect(widget.getAttribute('data-active-control')).toBe('cell'); - - await keydown('Enter'); - expect(widget.getAttribute('data-active-control')).toBe('widget'); - }); - - it('should set tabindex="0" on the active widget', async () => { - const gridData = createGridData(); - gridData[1].cells[1].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - const widget = gridElement.querySelector('#w1') as HTMLElement; - - await pointerDown(cell); - expect(widget.getAttribute('tabindex')).toBe('0'); - }); - - it('should set tabindex="-1" on inactive widgets', async () => { - const gridData = createGridData(); - gridData[1].cells[1].widgets = [{id: 'w1', type: 'simple'}]; - gridData[1].cells[2].widgets = [{id: 'w2', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widget1 = gridElement.querySelector('#w1') as HTMLElement; - const widget2 = gridElement.querySelector('#w2') as HTMLElement; - - const cell = gridElement.querySelector('#c1-1') as HTMLElement; - await pointerDown(cell); - - expect(widget1.getAttribute('tabindex')).toBe('0'); - expect(widget2.getAttribute('tabindex')).toBe('-1'); - }); - }); - - describe('widget activation', () => { - it('should immediately delegate focus when widgetType is simple', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widget = gridElement.querySelector('#w1') as HTMLElement; - - await tabIntoGrid(); - - expect(document.activeElement).toBe(widget); - }); - - it('should wait for enter key to delegate focus when widgetType is complex', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widgetDirective = fixture.debugElement - .query(By.css('#w1')) - .injector.get(GridCellWidget); - - await tabIntoGrid(); - - expect(widgetDirective.isActivated()).toBeFalse(); - - await keydown('Enter'); - expect(widgetDirective.isActivated()).toBeTrue(); - }); - - it('should enter edit mode when widgetType is editable and enter is pressed', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'editable'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widgetDirective = fixture.debugElement - .query(By.css('#w1')) - .injector.get(GridCellWidget); - - await tabIntoGrid(); - - await keydown('Enter'); - expect(widgetDirective.isActivated()).toBeTrue(); - }); - - it('should give widget tabindex -1 when focusTarget is present', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex', hasTarget: true}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widgetElement = gridElement.querySelector('#w1') as HTMLElement; - expect(widgetElement.getAttribute('tabindex')).toBe('-1'); - }); - - it('should emit the activated output on Enter for simple widget', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - await tabIntoGrid(); - - expect(fixture.componentInstance.onActivated).not.toHaveBeenCalled(); - - await keydown('Enter'); - expect(fixture.componentInstance.onActivated).toHaveBeenCalled(); - }); - - it('should emit the activated output on Space for simple widget', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - await tabIntoGrid(); - - expect(fixture.componentInstance.onActivated).not.toHaveBeenCalled(); - - await keydown(' '); - expect(fixture.componentInstance.onActivated).toHaveBeenCalled(); - }); - - it('should emit the activated output in activedescendant mode when event is dispatched directly to grid', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'simple'}]; - await setupGrid({gridData, focusMode: 'activedescendant'}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - expect(fixture.componentInstance.onActivated).not.toHaveBeenCalled(); - - // Verify standard activedescendant behavior by targeting the CONTAINER directly - const event = new KeyboardEvent('keydown', { - key: 'Enter', - bubbles: true, - }); - gridElement.dispatchEvent(event); - await fixture.whenStable(); - - expect(fixture.componentInstance.onActivated).toHaveBeenCalled(); - }); - - it('should emit the activated output when the widget becomes active', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - await tabIntoGrid(); - - expect(fixture.componentInstance.onActivated).not.toHaveBeenCalled(); - - await keydown('Enter'); - expect(fixture.componentInstance.onActivated).toHaveBeenCalled(); - }); - - it('should emit the deactivated output when the widget loses active state', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - await tabIntoGrid(); - - await keydown('Enter'); - expect(fixture.componentInstance.onDeactivated).not.toHaveBeenCalled(); - - await keydown('Escape'); - expect(fixture.componentInstance.onDeactivated).toHaveBeenCalled(); - }); - - it('should become active when activate() is called programmatically', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widgetDirective = fixture.debugElement - .query(By.css('#w1')) - .injector.get(GridCellWidget); - - widgetDirective.activate(); - await fixture.whenStable(); - - expect(widgetDirective.isActivated()).toBeTrue(); - expect(fixture.componentInstance.onActivated).toHaveBeenCalled(); - }); - - it('should lose active state when deactivate() is called programmatically', async () => { - const gridData = createGridData(); - gridData[0].cells[0].widgets = [{id: 'w1', type: 'complex'}]; - await setupGrid({gridData}); - gridInstance._pattern.setDefaultStateEffect(); - await fixture.whenStable(); - - const widgetDirective = fixture.debugElement - .query(By.css('#w1')) - .injector.get(GridCellWidget); - - widgetDirective.activate(); - await fixture.whenStable(); - - expect(widgetDirective.isActivated()).toBeTrue(); - - widgetDirective.deactivate(); - await fixture.whenStable(); - - expect(widgetDirective.isActivated()).toBeFalse(); - }); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupGrid(); - }); - - it('should warn when ngGridRow contains no cells', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [GridRowWithoutCells], - }); - const noCellsFixture = TestBed.createComponent(GridRowWithoutCells); - noCellsFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith('ngGridRow must contain at least one ngGridCell.'); - }); - }); -}); - -@Component({ - template: ` - - @for (row of gridData(); track row; let rIndex = $index) { - - @for (cell of row.cells; track cell; let cIndex = $index) { - - } - - } -
      - Cell {{rIndex}}-{{cIndex}} - @for (widget of cell.widgets || []; track $index; let wIndex = $index) { - @if (widget.hasTarget) { - - - } @else { - - } - } -
      - `, - imports: [Grid, GridRow, GridCell, GridCellWidget], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class GridTestComponent { - readonly disabled = signal(false); - readonly multi = signal(false); - readonly rowWrap = signal<'continuous' | 'loop' | 'nowrap'>('loop'); - readonly colWrap = signal<'continuous' | 'loop' | 'nowrap'>('loop'); - readonly focusMode = signal<'roving' | 'activedescendant'>('roving'); - readonly softDisabled = signal(true); - readonly enableSelection = signal(false); - readonly selectionMode = signal<'follow' | 'explicit'>('follow'); - readonly gridData = signal(createGridData()); - readonly tabIndex = signal(undefined); - - onActivated = jasmine.createSpy('activated'); - onDeactivated = jasmine.createSpy('deactivated'); -} - -@Component({ - template: ` - - -
      - `, - imports: [Grid, GridRow], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class GridRowWithoutCells {} diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index ebc1a928c7d0..c54b4d5a95bb 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -7,28 +7,20 @@ */ import { - afterNextRender, afterRenderEffect, booleanAttribute, computed, + contentChildren, Directive, ElementRef, inject, input, - OnDestroy, + NgZone, Signal, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; -import { - GridPattern, - GridCellPattern, - GridRowPattern, - SortedCollection, - tabIndexTransform, - reportViolations, -} from '../private'; -import {GridRow} from './grid-row'; -import {GRID} from './grid-tokens'; +import {GridPattern, GridCellPattern} from '../private'; +import {GRID_ROW} from './grid-tokens'; /** * The container for a grid. It provides keyboard navigation and focus management for the grid's @@ -49,6 +41,8 @@ import {GRID} from './grid-tokens'; * * ``` * + * @developerPreview 21.0 + * * @see [Grid](guide/aria/grid) */ @Directive({ @@ -56,31 +50,28 @@ import {GRID} from './grid-tokens'; exportAs: 'ngGrid', host: { 'role': 'grid', - '[tabindex]': 'tabIndex() !== undefined ? tabIndex() : _pattern.tabIndex()', + '[tabindex]': '_pattern.tabIndex()', '[attr.aria-disabled]': '_pattern.disabled()', - '[attr.aria-multiselectable]': '_pattern.multiSelectable()', '[attr.aria-activedescendant]': '_pattern.activeDescendant()', '(keydown)': '_pattern.onKeydown($event)', - '(click)': '_pattern.onClick($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(pointerup)': '_pattern.onPointerup($event)', '(focusin)': '_pattern.onFocusIn($event)', '(focusout)': '_pattern.onFocusOut($event)', }, - providers: [{provide: GRID, useExisting: Grid}], }) -export class Grid implements OnDestroy { +export class Grid { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of rows in the grid. */ - readonly _collection = new SortedCollection(); + /** The rows that make up the grid. */ + private readonly _rows = contentChildren(GRID_ROW, {descendants: true}); /** The UI patterns for the rows in the grid. */ - private readonly _rowPatterns: Signal = computed(() => - this._collection.orderedItems().map(r => r._pattern), - ); + private readonly _rowPatterns: Signal = computed(() => this._rows().map(r => r._pattern)); /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; @@ -130,11 +121,8 @@ export class Grid implements OnDestroy { */ readonly selectionMode = input<'follow' | 'explicit'>('follow'); - /** The tabindex of the grid. */ - readonly tabIndex = input(undefined, { - alias: 'tabindex', - transform: tabIndexTransform, - }); + /** Whether enable range selections (with modifier keys or dragging). */ + readonly enableRangeSelection = input(false, {transform: booleanAttribute}); /** The UI pattern for the grid. */ readonly _pattern = new GridPattern({ @@ -144,38 +132,28 @@ export class Grid implements OnDestroy { element: () => this.element, }); - /** The ID of the active descendant in the grid. */ - readonly activeDescendant = computed(() => this._pattern.activeDescendant()); - constructor() { - // Use Write mode for all direct DOM focus management actions. - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); - afterRenderEffect({write: () => this._pattern.resetStateEffect()}); - afterRenderEffect({write: () => this._pattern.resetFocusEffect()}); - afterRenderEffect({write: () => this._pattern.restoreFocusEffect()}); - afterRenderEffect({write: () => this._pattern.focusEffect()}); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); + const ngZone = inject(NgZone); + + // Since `pointermove` fires on each pixel, we need to + // be careful not to hit the zone unless it's necessary. + ngZone.runOutsideAngular(() => { + this.element.addEventListener( + 'pointermove', + event => { + if (this._pattern.acceptsPointerMove()) { + ngZone.run(() => this._pattern.onPointermove(event)); + } }, - }); - } - - afterNextRender(() => { - this._collection.startObserving(this.element); + {passive: true}, + ); }); - } - - ngOnDestroy() { - this._collection.stopObserving(); - } - /** Scrolls the active cell into view. */ - scrollActiveCellIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { - this._pattern.activeCell()?.element().scrollIntoView(options); + afterRenderEffect(() => this._pattern.setDefaultStateEffect()); + afterRenderEffect(() => this._pattern.resetStateEffect()); + afterRenderEffect(() => this._pattern.resetFocusEffect()); + afterRenderEffect(() => this._pattern.restoreFocusEffect()); + afterRenderEffect(() => this._pattern.focusEffect()); } /** Gets the cell pattern for a given element. */ diff --git a/src/aria/grid/testing/BUILD.bazel b/src/aria/grid/testing/BUILD.bazel deleted file mode 100644 index 58e8a2f3f7f3..000000000000 --- a/src/aria/grid/testing/BUILD.bazel +++ /dev/null @@ -1,42 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/aria/grid", - "//src/cdk/testing", - "//src/cdk/testing/private", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [ - ":unit_tests_lib", - ], -) diff --git a/src/aria/grid/testing/grid-harness-filters.ts b/src/aria/grid/testing/grid-harness-filters.ts deleted file mode 100644 index 857a1d0a9953..000000000000 --- a/src/aria/grid/testing/grid-harness-filters.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** Filters for locating a `GridCellHarness`. */ -export interface GridCellHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose text matches the given value. */ - text?: string | RegExp; - /** Only find instances whose selected state matches the given value. */ - selected?: boolean; - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; -} - -/** Filters for locating a `GridRowHarness`. */ -export interface GridRowHarnessFilters extends BaseHarnessFilters { - // Add filters if needed, e.g., rowIndex -} - -/** Filters for locating a `GridHarness`. */ -export interface GridHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; -} diff --git a/src/aria/grid/testing/grid-harness.spec.ts b/src/aria/grid/testing/grid-harness.spec.ts deleted file mode 100644 index 02f91d10f5f5..000000000000 --- a/src/aria/grid/testing/grid-harness.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; - -import {GridHarness, GridRowHarness, GridCellHarness} from './grid-harness'; -import {Grid, GridRow, GridCell} from '../index'; - -describe('Grid Harness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [GridHarnessTestComponent], - }); - fixture = TestBed.createComponent(GridHarnessTestComponent); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('finds the grid harness', async () => { - await expectAsync(loader.getHarness(GridHarness)).toBeResolved(); - }); - - it('returns all rows scoped within the grid', async () => { - const grid = await loader.getHarness(GridHarness); - const rows = await grid.getRows(); - expect(rows.length).toBe(2); - }); - - it('returns all cells scoped within the grid', async () => { - const grid = await loader.getHarness(GridHarness); - const cells = await grid.getCells(); - expect(cells.length).toBe(4); - }); - - it('returns cells scoped within a row', async () => { - const rows = await loader.getAllHarnesses(GridRowHarness); - expect(rows.length).toBe(2); - - const cellsInRow0 = await rows[0].getCells(); - expect(cellsInRow0.length).toBe(2); - expect(await cellsInRow0[0].getText()).toBe('Cell 1.1'); - - const cellsInRow1 = await rows[1].getCells(); - expect(cellsInRow1.length).toBe(2); - expect(await cellsInRow1[0].getText()).toBe('Cell 2.1'); - }); - - it('filters cells by exact text content', async () => { - const grid = await loader.getHarness(GridHarness); - const cells = await grid.getCells({text: 'Cell 1.1'}); - expect(cells.length).toBe(1); - }); - - it('reports the disabled state of the grid', async () => { - const grid = await loader.getHarness(GridHarness); - const isDisabled = await grid.isDisabled(); - expect(isDisabled).toBeFalse(); - }); - - it('reports the multi-selectable state of the grid', async () => { - const grid = await loader.getHarness(GridHarness); - const isMulti = await grid.isMultiSelectable(); - expect(isMulti).toBeTrue(); - }); - - it('reports the selected state of a cell', async () => { - const cell = await loader.getHarness(GridCellHarness.with({text: 'Cell 1.1'})); - expect(await cell.isSelected()).toBeTrue(); - }); - - it('reports the disabled state of a cell', async () => { - const cell = await loader.getHarness(GridCellHarness.with({text: 'Cell 2.2'})); - expect(await cell.isDisabled()).toBeTrue(); - }); - - it('gets the text of cells organized by rows', async () => { - const grid = await loader.getHarness(GridHarness); - const text = await grid.getCellTextByIndex(); - expect(text).toEqual([ - ['Cell 1.1', 'Cell 1.2'], - ['Cell 2.1', 'Cell 2.2'], - ]); - }); - - it('gets the text of cells in a row', async () => { - const rows = await loader.getAllHarnesses(GridRowHarness); - expect(await rows[0].getCellTextByIndex()).toEqual(['Cell 1.1', 'Cell 1.2']); - }); - - it('reports the active state of a cell', async () => { - const cell = await loader.getHarness(GridCellHarness.with({text: 'Cell 1.1'})); - expect(await cell.isActive()).toBeTrue(); - }); - - it('reports the focused state of a cell', async () => { - const cell = await loader.getHarness(GridCellHarness.with({text: 'Cell 1.1'})); - expect(await cell.isFocused()).toBeFalse(); - - await cell.focus(); - expect(await cell.isFocused()).toBeTrue(); - }); -}); - -@Component({ - imports: [Grid, GridRow, GridCell], - template: ` - - - - - - - - - -
      Cell 1.1Cell 1.2
      Cell 2.1Cell 2.2
      - `, -}) -class GridHarnessTestComponent {} diff --git a/src/aria/grid/testing/grid-harness.ts b/src/aria/grid/testing/grid-harness.ts deleted file mode 100644 index f3a1efb55a3a..000000000000 --- a/src/aria/grid/testing/grid-harness.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - ComponentHarness, - ContentContainerComponentHarness, - HarnessPredicate, - parallel, -} from '@angular/cdk/testing'; -import { - GridHarnessFilters, - GridRowHarnessFilters, - GridCellHarnessFilters, -} from './grid-harness-filters'; - -/** Harness for interacting with a standard ngGridCell in tests. */ -export class GridCellHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngGridCell]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a grid cell with specific attributes. - * @param options Options for filtering which cell instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: GridCellHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(GridCellHarness, options) - .addOption('text', options.text, (harness, text) => - HarnessPredicate.stringMatches(harness.getText(), text), - ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Whether the cell is selected. */ - async isSelected(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-selected')) === 'true'; - } - - /** Whether the cell is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets the text content of the cell. */ - async getText(): Promise { - const host = await this.host(); - return host.text(); - } - - /** Clicks the cell. */ - async click(): Promise { - const host = await this.host(); - return host.click(); - } - - /** Focuses the cell. */ - async focus(): Promise { - const host = await this.host(); - return host.focus(); - } - - /** Blurs the cell. */ - async blur(): Promise { - const host = await this.host(); - return host.blur(); - } - - /** Whether the cell is active. */ - async isActive(): Promise { - const host = await this.host(); - return (await host.getAttribute('data-active')) === 'true'; - } - - /** Whether the cell is focused. */ - async isFocused(): Promise { - const host = await this.host(); - return host.isFocused(); - } -} - -/** Harness for interacting with a standard ngGridRow in tests. */ -export class GridRowHarness extends ComponentHarness { - static hostSelector = '[ngGridRow]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a grid row with specific attributes. - * @param options Options for filtering which row instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: GridRowHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(GridRowHarness, options); - } - - /** Gets all cells in the row. */ - async getCells(filters: GridCellHarnessFilters = {}): Promise { - return this.locatorForAll(GridCellHarness.with(filters))(); - } - - /** Gets the text of the cells in the row. */ - async getCellTextByIndex(filters: GridCellHarnessFilters = {}): Promise { - const cells = await this.getCells(filters); - return parallel(() => cells.map(cell => cell.getText())); - } -} - -/** Harness for interacting with a standard ngGrid in tests. */ -export class GridHarness extends ComponentHarness { - static hostSelector = '[ngGrid]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a grid with specific attributes. - * @param options Options for filtering which grid instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: GridHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(GridHarness, options).addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Whether the grid is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Whether the grid is multi-selectable. */ - async isMultiSelectable(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-multiselectable')) === 'true'; - } - - /** Gets all rows in the grid. */ - async getRows(filters: GridRowHarnessFilters = {}): Promise { - return this.locatorForAll(GridRowHarness.with(filters))(); - } - - /** Gets all cells in the grid. */ - async getCells(filters: GridCellHarnessFilters = {}): Promise { - return this.locatorForAll(GridCellHarness.with(filters))(); - } - - /** Gets the text inside the entire grid organized by rows. */ - async getCellTextByIndex(): Promise { - const rows = await this.getRows(); - return parallel(() => rows.map(row => row.getCellTextByIndex())); - } -} diff --git a/src/aria/grid/testing/index.ts b/src/aria/grid/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/grid/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/grid/testing/public-api.ts b/src/aria/grid/testing/public-api.ts deleted file mode 100644 index c6102e2b7d17..000000000000 --- a/src/aria/grid/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './grid-harness'; -export * from './grid-harness-filters'; diff --git a/src/aria/listbox/BUILD.bazel b/src/aria/listbox/BUILD.bazel index 303fa9829906..4ba1e872d6eb 100644 --- a/src/aria/listbox/BUILD.bazel +++ b/src/aria/listbox/BUILD.bazel @@ -10,11 +10,10 @@ ng_project( ), deps = [ "//:node_modules/@angular/core", + "//src/aria/combobox", "//src/aria/private", - "//src/aria/private/utils", "//src/cdk/a11y", "//src/cdk/bidi", - "//src/cdk/testing", ], ) @@ -30,10 +29,7 @@ ng_project( "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", "//:node_modules/axe-core", - "//src/aria/private/testing", - "//src/cdk/testing", "//src/cdk/testing/private", - "//src/cdk/testing/testbed", ], ) diff --git a/src/aria/listbox/listbox.spec.ts b/src/aria/listbox/listbox.spec.ts index d397ed8f02a8..caa6e3f24e7a 100644 --- a/src/aria/listbox/listbox.spec.ts +++ b/src/aria/listbox/listbox.spec.ts @@ -1,11 +1,10 @@ -import {Component, DebugElement, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, DebugElement, signal} from '@angular/core'; import {Listbox} from './listbox'; import {Option} from './option'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; interface ModifierKeys { ctrlKey?: boolean; @@ -22,7 +21,7 @@ describe('Listbox', () => { let listboxElement: HTMLElement; let optionElements: HTMLElement[]; - const keydown = async (key: string, modifierKeys: ModifierKeys = {}) => { + const keydown = (key: string, modifierKeys: ModifierKeys = {}) => { listboxElement.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -30,34 +29,31 @@ describe('Listbox', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (index: number, eventInit?: PointerEventInit, targets?: HTMLElement[]) => { + const click = (index: number, eventInit?: PointerEventInit, targets?: HTMLElement[]) => { (targets || optionElements)[index].dispatchEvent( - new PointerEvent('click', { - bubbles: true, - ...eventInit, - }), + new PointerEvent('pointerdown', {bubbles: true, ...eventInit}), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const space = async (modifierKeys?: ModifierKeys) => await keydown(' ', modifierKeys); - const enter = async (modifierKeys?: ModifierKeys) => await keydown('Enter', modifierKeys); - const up = async (modifierKeys?: ModifierKeys) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: ModifierKeys) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: ModifierKeys) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: ModifierKeys) => await keydown('ArrowRight', modifierKeys); - const home = async (modifierKeys?: ModifierKeys) => await keydown('Home', modifierKeys); - const end = async (modifierKeys?: ModifierKeys) => await keydown('End', modifierKeys); - const type = async (char: string) => await keydown(char); - - async function setupListbox(opts?: { + const space = (modifierKeys?: ModifierKeys) => keydown(' ', modifierKeys); + const enter = (modifierKeys?: ModifierKeys) => keydown('Enter', modifierKeys); + const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys); + const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys); + const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys); + const right = (modifierKeys?: ModifierKeys) => keydown('ArrowRight', modifierKeys); + const home = (modifierKeys?: ModifierKeys) => keydown('Home', modifierKeys); + const end = (modifierKeys?: ModifierKeys) => keydown('End', modifierKeys); + const type = (char: string) => keydown(char); + + function setupListbox(opts?: { orientation?: 'horizontal' | 'vertical'; disabled?: boolean; readonly?: boolean; - value?: number[]; + values?: number[]; softDisabled?: boolean; focusMode?: 'roving' | 'activedescendant'; multi?: boolean; @@ -67,7 +63,6 @@ describe('Listbox', () => { disabledOptions?: number[]; options?: TestOption[]; textDirection?: Direction; - tabIndex?: number; }) { TestBed.configureTestingModule({ providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], @@ -79,7 +74,7 @@ describe('Listbox', () => { if (opts?.orientation !== undefined) testComponent.orientation = opts.orientation; if (opts?.disabled !== undefined) testComponent.disabled = opts.disabled; if (opts?.readonly !== undefined) testComponent.readonly = opts.readonly; - if (opts?.value !== undefined) testComponent.value = opts.value; + if (opts?.values !== undefined) testComponent.values = opts.values; if (opts?.softDisabled !== undefined) testComponent.softDisabled = opts.softDisabled; if (opts?.focusMode !== undefined) testComponent.focusMode = opts.focusMode; if (opts?.multi !== undefined) testComponent.multi = opts.multi; @@ -87,7 +82,6 @@ describe('Listbox', () => { if (opts?.selectionMode !== undefined) testComponent.selectionMode = opts.selectionMode; if (opts?.typeaheadDelay !== undefined) testComponent.typeaheadDelay = opts.typeaheadDelay; if (opts?.options !== undefined) testComponent.options.set(opts.options); - if (opts?.tabIndex !== undefined) testComponent.tabIndex = opts.tabIndex; if (opts?.disabledOptions !== undefined) { const currentOptions = testComponent.options(); @@ -97,11 +91,11 @@ describe('Listbox', () => { testComponent.options.set([...currentOptions]); } - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(fixture); } - async function setupDefaultListbox() { + function setupDefaultListbox() { TestBed.configureTestingModule({ providers: [provideFakeDirectionality('ltr')], }); @@ -123,7 +117,7 @@ describe('Listbox', () => { describe('ARIA attributes and roles', () => { describe('default configuration', () => { - beforeEach(async () => await setupDefaultListbox()); + beforeEach(() => setupDefaultListbox()); it('should correctly set the role attribute to "listbox"', () => { expect(listboxElement.getAttribute('role')).toBe('listbox'); @@ -151,41 +145,36 @@ describe('Listbox', () => { expect(listboxElement.getAttribute('aria-multiselectable')).toBe('false'); }); - it('should set aria-selected to "true" for the first option and "false" for others by default', () => { - expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); - expect(optionElements[1].getAttribute('aria-selected')).toBe('false'); - expect(optionElements[2].getAttribute('aria-selected')).toBe('false'); + it('should set aria-selected to "false" for all options by default', () => { + optionElements.forEach(optionElement => { + expect(optionElement.getAttribute('aria-selected')).toBe('false'); + }); }); }); describe('custom configuration', () => { - it('should be able to set aria-orientation to "horizontal"', async () => { - await setupListbox({orientation: 'horizontal'}); + it('should be able to set aria-orientation to "horizontal"', () => { + setupListbox({orientation: 'horizontal'}); expect(listboxElement.getAttribute('aria-orientation')).toBe('horizontal'); }); - it('should be able to set aria-disabled to "true"', async () => { - await setupListbox({disabled: true}); + it('should be able to set aria-disabled to "true"', () => { + setupListbox({disabled: true}); expect(listboxElement.getAttribute('aria-disabled')).toBe('true'); }); - it('should be able to set aria-readonly to "true"', async () => { - await setupListbox({readonly: true}); + it('should be able to set aria-readonly to "true"', () => { + setupListbox({readonly: true}); expect(listboxElement.getAttribute('aria-readonly')).toBe('true'); }); - it('should be able to set aria-multiselectable to "true"', async () => { - await setupListbox({multi: true}); + it('should be able to set aria-multiselectable to "true"', () => { + setupListbox({multi: true}); expect(listboxElement.getAttribute('aria-multiselectable')).toBe('true'); }); - it('should be able to override tabindex', async () => { - await setupListbox({tabIndex: -1}); - expect(listboxElement.getAttribute('tabindex')).toBe('-1'); - }); - - it('should set aria-selected to "true" for selected options', async () => { - await setupListbox({multi: true, value: [1, 3]}); + it('should set aria-selected to "true" for selected options', () => { + setupListbox({multi: true, values: [1, 3]}); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[2].getAttribute('aria-selected')).toBe('false'); @@ -193,8 +182,8 @@ describe('Listbox', () => { expect(optionElements[4].getAttribute('aria-selected')).toBe('false'); }); - it('should set aria-disabled to "true" for disabled options', async () => { - await setupListbox({disabledOptions: [1]}); + it('should set aria-disabled to "true" for disabled options', () => { + setupListbox({disabledOptions: [1]}); expect(optionElements[0].getAttribute('aria-disabled')).toBe('false'); expect(optionElements[1].getAttribute('aria-disabled')).toBe('true'); expect(optionElements[2].getAttribute('aria-disabled')).toBe('false'); @@ -202,18 +191,18 @@ describe('Listbox', () => { }); describe('roving focus mode', () => { - it('should have tabindex="-1" for the listbox when focusMode is "roving"', async () => { - await setupListbox({focusMode: 'roving'}); + it('should have tabindex="-1" for the listbox when focusMode is "roving"', () => { + setupListbox({focusMode: 'roving'}); expect(listboxElement.getAttribute('tabindex')).toBe('-1'); }); - it('should set tabindex="0" for the listbox when disabled and focusMode is "roving"', async () => { - await setupListbox({disabled: true, focusMode: 'roving'}); + it('should set tabindex="0" for the listbox when disabled and focusMode is "roving"', () => { + setupListbox({disabled: true, focusMode: 'roving'}); expect(listboxElement.getAttribute('tabindex')).toBe('0'); }); - it('should set initial focus (tabindex="0") on the first non-disabled option if no values are set', async () => { - await setupListbox({focusMode: 'roving'}); + it('should set initial focus (tabindex="0") on the first non-disabled option if no values are set', () => { + setupListbox({focusMode: 'roving'}); expect(optionElements[0].getAttribute('tabindex')).toBe('0'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); expect(optionElements[2].getAttribute('tabindex')).toBe('-1'); @@ -221,8 +210,8 @@ describe('Listbox', () => { expect(optionElements[4].getAttribute('tabindex')).toBe('-1'); }); - it('should set initial focus (tabindex="0") on the first selected option', async () => { - await setupListbox({focusMode: 'roving', value: [2]}); + it('should set initial focus (tabindex="0") on the first selected option', () => { + setupListbox({focusMode: 'roving', values: [2]}); expect(optionElements[0].getAttribute('tabindex')).toBe('-1'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); expect(optionElements[2].getAttribute('tabindex')).toBe('0'); @@ -230,10 +219,10 @@ describe('Listbox', () => { expect(optionElements[4].getAttribute('tabindex')).toBe('-1'); }); - it('should set initial focus (tabindex="0") on the first non-disabled option if selected option is disabled when softDisabled is false', async () => { - await setupListbox({ + it('should set initial focus (tabindex="0") on the first non-disabled option if selected option is disabled when softDisabled is false', () => { + setupListbox({ focusMode: 'roving', - value: [1], + values: [1], disabledOptions: [0], softDisabled: false, }); @@ -241,55 +230,55 @@ describe('Listbox', () => { expect(optionElements[1].getAttribute('tabindex')).toBe('0'); }); - it('should set initial focus (tabindex="0") on the first option if selected option is disabled', async () => { - await setupListbox({ + it('should set initial focus (tabindex="0") on the first option if selected option is disabled', () => { + setupListbox({ focusMode: 'roving', - value: [0], + values: [0], disabledOptions: [0], }); expect(optionElements[0].getAttribute('tabindex')).toBe('0'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); }); - it('should not have aria-activedescendant when focusMode is "roving"', async () => { - await setupListbox({focusMode: 'roving'}); + it('should not have aria-activedescendant when focusMode is "roving"', () => { + setupListbox({focusMode: 'roving'}); expect(listboxElement.hasAttribute('aria-activedescendant')).toBe(false); }); }); describe('activedescendant focus mode', () => { - it('should have tabindex="0" for the listbox', async () => { - await setupListbox({focusMode: 'activedescendant'}); + it('should have tabindex="0" for the listbox', () => { + setupListbox({focusMode: 'activedescendant'}); expect(listboxElement.getAttribute('tabindex')).toBe('0'); }); - it('should set aria-activedescendant to the ID of the first non-disabled option if no value is set', async () => { - await setupListbox({focusMode: 'activedescendant'}); + it('should set aria-activedescendant to the ID of the first non-disabled option if no value is set', () => { + setupListbox({focusMode: 'activedescendant'}); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[0].id); }); - it('should set aria-activedescendant to the ID of the first selected option', async () => { - await setupListbox({focusMode: 'activedescendant', value: [2]}); + it('should set aria-activedescendant to the ID of the first selected option', () => { + setupListbox({focusMode: 'activedescendant', values: [2]}); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[2].id); }); - it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled', async () => { - await setupListbox({focusMode: 'activedescendant', value: [0], disabledOptions: [0]}); + it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled', () => { + setupListbox({focusMode: 'activedescendant', values: [0], disabledOptions: [0]}); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[0].id); }); - it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled when softDisabled is false', async () => { - await setupListbox({ + it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled when softDisabled is false', () => { + setupListbox({ focusMode: 'activedescendant', - value: [1], + values: [1], disabledOptions: [0], softDisabled: false, }); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[1].id); }); - it('should set tabindex="-1" for all options', async () => { - await setupListbox({focusMode: 'activedescendant'}); + it('should set tabindex="-1" for all options', () => { + setupListbox({focusMode: 'activedescendant'}); expect(optionElements[0].getAttribute('tabindex')).toBe('-1'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); expect(optionElements[2].getAttribute('tabindex')).toBe('-1'); @@ -300,44 +289,44 @@ describe('Listbox', () => { }); describe('value and selection', () => { - it('should select the options corresponding to the value input', async () => { - await setupListbox({multi: true, value: [1, 3]}); + it('should select the options corresponding to the value input', () => { + setupListbox({multi: true, values: [1, 3]}); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[3].getAttribute('aria-selected')).toBe('true'); - expect(listboxInstance.value()).toEqual([1, 3]); + expect(listboxInstance.values()).toEqual([1, 3]); }); - it('should update the value model when an option is selected via UI (single select)', async () => { - await setupListbox({multi: false}); - await click(1); - expect(listboxInstance.value()).toEqual([1]); - await click(2); - expect(listboxInstance.value()).toEqual([2]); + it('should update the value model when an option is selected via UI (single select)', () => { + setupListbox({multi: false}); + click(1); + expect(listboxInstance.values()).toEqual([1]); + click(2); + expect(listboxInstance.values()).toEqual([2]); }); - it('should update the value model when options are selected via UI (multi select)', async () => { - await setupListbox({multi: true}); - await click(1); - expect(listboxInstance.value()).toEqual([1]); - await click(3); - expect(listboxInstance.value()).toEqual([1, 3]); - await click(1); - expect(listboxInstance.value()).toEqual([3]); + it('should update the value model when options are selected via UI (multi select)', () => { + setupListbox({multi: true}); + click(1); + expect(listboxInstance.values()).toEqual([1]); + click(3); + expect(listboxInstance.values()).toEqual([1, 3]); + click(1); + expect(listboxInstance.values()).toEqual([3]); }); describe('pointer interactions', () => { describe('single select', () => { - it('should select an option on click', async () => { - await setupListbox({multi: false}); - await click(1); - expect(listboxInstance.value()).toEqual([1]); + it('should select an option on click', () => { + setupListbox({multi: false}); + click(1); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should select a new option and deselect the old one on click', async () => { - await setupListbox({multi: false, value: [0]}); - await click(1); - expect(listboxInstance.value()).toEqual([1]); + it('should select a new option and deselect the old one on click', () => { + setupListbox({multi: false, values: [0]}); + click(1); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); @@ -345,230 +334,148 @@ describe('Listbox', () => { describe('multi select', () => { describe('selection follows focus', () => { - it('should select only the clicked option with a simple click', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await click(1); - expect(listboxInstance.value()).toEqual([1]); + it('should select only the clicked option with a simple click', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + click(1); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should toggle the selected state of an option with ctrl + click', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await click(1, {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + it('should toggle the selected state of an option with ctrl + click', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + click(1, {ctrlKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); - await click(0, {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([1]); + click(0, {ctrlKey: true}); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should select a range starting from the first option on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await click(2, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + it('should select a range starting from the first option on shift + click', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + click(2, {shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[2].getAttribute('aria-selected')).toBe('true'); }); - it('should select a range starting from the current active option on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'follow'}); - await click(1); - await click(3, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([1, 2, 3]); + it('should select a range starting from the current active option on shift + click', () => { + setupListbox({multi: true, selectionMode: 'follow'}); + click(1); + click(3, {shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([1, 2, 3]); }); - it('should not select disabled options on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); - await click(2, {shiftKey: true}); - expect(listboxInstance.value()).toEqual([0, 2]); + it('should not select disabled options on shift + click', () => { + setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); + click(2, {shiftKey: true}); + expect(listboxInstance.values()).toEqual([0, 2]); }); }); describe('explicit selection', () => { - it('should toggle selection of the clicked option with a simple click', async () => { - await setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); - await click(1); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + it('should toggle selection of the clicked option with a simple click', () => { + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); + click(1); + expect(listboxInstance.values().sort()).toEqual([0, 1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); - await click(0); - expect(listboxInstance.value()).toEqual([1]); + click(0); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); }); - it('should select a range starting from the first option on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); - await click(2, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + it('should select a range starting from the first option on shift + click', () => { + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); + click(2, {shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); - it('should select a range starting from the current active option on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await click(1); - await click(3, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([1, 2, 3]); + it('should select a range starting from the current active option on shift + click', () => { + setupListbox({multi: true, selectionMode: 'explicit'}); + click(1); + click(3, {shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([1, 2, 3]); }); - it('should not select disabled options on shift + click', async () => { - await setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); - await click(2, {shiftKey: true}); - expect(listboxInstance.value()).toEqual([0, 2]); + it('should not select disabled options on shift + click', () => { + setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); + click(2, {shiftKey: true}); + expect(listboxInstance.values()).toEqual([0, 2]); }); }); }); }); - describe('with shuffled items', () => { - it('should update collection order when items are shuffled', async () => { - await setupListbox({ - options: [ - {value: 1, label: 'Item 1', disabled: false}, - {value: 2, label: 'Item 2', disabled: false}, - {value: 3, label: 'Item 3', disabled: false}, - ], - }); - - // Verify initial DOM order - expect(optionElements.length).toBe(3); - expect(optionElements[0].textContent?.trim()).toBe('Item 1'); - expect(optionElements[2].textContent?.trim()).toBe('Item 3'); - - const testComponent = fixture.componentInstance as ListboxExample; - const items = testComponent.options().reverse(); - testComponent.options.set([...items]); - await fixture.whenStable(); - await waitForMicrotasks(); - - // Re-query elements to check new DOM order - defineTestVariables(fixture); - - expect(optionElements.length).toBe(3); - expect(optionElements[0].textContent?.trim()).toBe('Item 3'); - expect(optionElements[2].textContent?.trim()).toBe('Item 1'); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupListbox(); - }); - - it('should warn when duplicate option values are detected inside ngListbox', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ListboxWithDuplicateValues], - }); - const duplicateFixture = TestBed.createComponent(ListboxWithDuplicateValues); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - "Duplicate option value 'item0' detected inside ngListbox.", - ); - }); - - it('should warn when duplicate option IDs are detected inside ngListbox', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ListboxWithDuplicateIds], - }); - const duplicateFixture = TestBed.createComponent(ListboxWithDuplicateIds); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - "Duplicate option ID 'option0' detected inside ngListbox.", - ); - }); - - it('should warn when single-select listbox has multiple selected options', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [SingleSelectListboxWithMultipleValues], - }); - const singleSelectFixture = TestBed.createComponent(SingleSelectListboxWithMultipleValues); - singleSelectFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'A single-select listbox should not have multiple selected options. Selected options: item0, item1', - ); - }); - }); - describe('keyboard interactions', () => { describe('single select', () => { describe('selection follows focus', () => { - it('should select the next option on ArrowDown', async () => { - await setupListbox({multi: false, selectionMode: 'follow'}); - await down(); - expect(listboxInstance.value()).toEqual([1]); + it('should select the next option on ArrowDown', () => { + setupListbox({multi: false, selectionMode: 'follow'}); + down(); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); - await down(); - expect(listboxInstance.value()).toEqual([2]); + down(); + expect(listboxInstance.values()).toEqual([2]); expect(optionElements[2].getAttribute('aria-selected')).toBe('true'); }); - it('should select the previous option on ArrowUp', async () => { - await setupListbox({multi: false, selectionMode: 'follow', value: [2]}); - await up(); - expect(listboxInstance.value()).toEqual([1]); + it('should select the previous option on ArrowUp', () => { + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); + up(); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should select the first option on Home', async () => { - await setupListbox({multi: false, selectionMode: 'follow', value: [2]}); - await home(); - expect(listboxInstance.value()).toEqual([0]); + it('should select the first option on Home', () => { + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); + home(); + expect(listboxInstance.values()).toEqual([0]); }); - it('should select the last option on End', async () => { - await setupListbox({multi: false, selectionMode: 'follow', value: [2]}); - await end(); - expect(listboxInstance.value()).toEqual([4]); + it('should select the last option on End', () => { + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); + end(); + expect(listboxInstance.values()).toEqual([4]); }); }); describe('explicit selection', () => { - it('should move focus but not select on navigation', async () => { - await setupListbox({multi: false, selectionMode: 'explicit'}); - await down(); - await up(); - await home(); - await end(); - expect(listboxInstance.value()).toEqual([]); + it('should move focus but not select on navigation', () => { + setupListbox({multi: false, selectionMode: 'explicit'}); + down(); + up(); + home(); + end(); + expect(listboxInstance.values()).toEqual([]); expect(optionElements[1].getAttribute('aria-selected')).toBe('false'); }); - it('should select the focused option on Space', async () => { - await setupListbox({multi: false, selectionMode: 'explicit'}); - await down(); - await space(); - expect(listboxInstance.value()).toEqual([1]); + it('should select the focused option on Space', () => { + setupListbox({multi: false, selectionMode: 'explicit'}); + down(); + space(); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); - await down(); - await down(); - await space(); - expect(listboxInstance.value()).toEqual([3]); + down(); + down(); + space(); + expect(listboxInstance.values()).toEqual([3]); expect(optionElements[1].getAttribute('aria-selected')).toBe('false'); expect(optionElements[3].getAttribute('aria-selected')).toBe('true'); }); - it('should select the focused option on Enter', async () => { - await setupListbox({multi: false, selectionMode: 'explicit'}); - await down(); - await enter(); - expect(listboxInstance.value()).toEqual([1]); + it('should select the focused option on Enter', () => { + setupListbox({multi: false, selectionMode: 'explicit'}); + down(); + enter(); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); }); @@ -576,102 +483,91 @@ describe('Listbox', () => { describe('multi select', () => { describe('selection follows focus', () => { - it('should select only the focused option on ArrowDown (no modifier)', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await down(); - expect(listboxInstance.value()).toEqual([1]); + it('should select only the focused option on ArrowDown (no modifier)', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + down(); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should move focus but not change selection on ctrl + ArrowDown, then toggle with ctrl + Space', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await down({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([0]); - await space({ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + it('should move focus but not change selection on ctrl + ArrowDown, then toggle with ctrl + Space', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + down({ctrlKey: true}); + expect(listboxInstance.values()).toEqual([0]); + space({ctrlKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1]); }); - it('should toggle selection of the focused item on ctrl + Space', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await space({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); - await down(); - expect(listboxInstance.value()).toEqual([1]); - await space({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); + it('should toggle selection of the focused item on ctrl + Space', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + space({ctrlKey: true}); + expect(listboxInstance.values()).toEqual([]); + down(); + expect(listboxInstance.values()).toEqual([1]); + space({ctrlKey: true}); + expect(listboxInstance.values()).toEqual([]); }); - it('should extend selection on shift + ArrowDown', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); - await down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + it('should extend selection on shift + ArrowDown', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + down({shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1]); + down({shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); - it('should select all on Ctrl+A, then select active on second Ctrl+A', async () => { - await setupListbox({multi: true, selectionMode: 'follow', value: [0]}); - await keydown('A', {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2, 3, 4]); + it('should select all on Ctrl+A, then select active on second Ctrl+A', () => { + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); + keydown('A', {ctrlKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2, 3, 4]); - await keydown('A', {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([0]); + keydown('A', {ctrlKey: true}); + expect(listboxInstance.values()).toEqual([0]); }); }); describe('explicit selection', () => { - it('should move focus but not select on ArrowDown', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await down(); - expect(listboxInstance.value()).toEqual([]); - }); - - it('should toggle selection of the focused item on Space', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await down(); - await space(); - expect(listboxInstance.value()).toEqual([1]); - await down(); - await space(); - expect(listboxInstance.value().sort()).toEqual([1, 2]); - await space(); - expect(listboxInstance.value()).toEqual([1]); + it('should move focus but not select on ArrowDown', () => { + setupListbox({multi: true, selectionMode: 'explicit'}); + down(); + expect(listboxInstance.values()).toEqual([]); }); - it('should toggle selection of the focused item on Enter', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await down(); - await enter(); - expect(listboxInstance.value()).toEqual([1]); + it('should toggle selection of the focused item on Space', () => { + setupListbox({multi: true, selectionMode: 'explicit'}); + down(); + space(); + expect(listboxInstance.values()).toEqual([1]); + down(); + space(); + expect(listboxInstance.values().sort()).toEqual([1, 2]); + space(); + expect(listboxInstance.values()).toEqual([1]); }); - it('should extend selection on Shift+ArrowDown', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); - await down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + it('should toggle selection of the focused item on Enter', () => { + setupListbox({multi: true, selectionMode: 'explicit'}); + down(); + enter(); + expect(listboxInstance.values()).toEqual([1]); }); - it('should move selection anchor along with focus during normal non-shift navigation', async () => { - await setupListbox({multi: true, selectionMode: 'explicit'}); - await down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); - await down(); - await down(); - await down(); - await up({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 3, 4]); + it('should extend selection on Shift+ArrowDown', () => { + setupListbox({multi: true, selectionMode: 'explicit'}); + down({shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1]); + down({shiftKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); - it('should toggle selection of all options on Ctrl+A', async () => { - await setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); - await keydown('A', {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2, 3, 4]); + it('should toggle selection of all options on Ctrl+A', () => { + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); + keydown('A', {ctrlKey: true}); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2, 3, 4]); - await keydown('A', {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); + keydown('A', {ctrlKey: true}); + expect(listboxInstance.values()).toEqual([]); }); }); }); @@ -683,92 +579,92 @@ describe('Listbox', () => { isFocused: (index: number) => boolean, ) { describe(`keyboard navigation (focusMode="${focusMode}")`, () => { - it('should move focus to the last focusable option on End', async () => { - await setupListbox({focusMode, disabledOptions: [4]}); - await end(); + it('should move focus to the last focusable option on End', () => { + setupListbox({focusMode, disabledOptions: [4]}); + end(); expect(isFocused(4)).toBe(true); }); - it('should move focus to the first focusable option on Home', async () => { - await setupListbox({focusMode, disabledOptions: [0]}); - await end(); - await home(); + it('should move focus to the first focusable option on Home', () => { + setupListbox({focusMode, disabledOptions: [0]}); + end(); + home(); expect(isFocused(0)).toBe(true); }); - it('should allow keyboard navigation if the group is readonly', async () => { - await setupListbox({focusMode, orientation: 'horizontal', readonly: true}); - await right(); + it('should allow keyboard navigation if the group is readonly', () => { + setupListbox({focusMode, orientation: 'horizontal', readonly: true}); + right(); expect(isFocused(1)).toBe(true); }); - it('should wrap focus from last to first with ArrowDown when wrap is true (vertical)', async () => { - await setupListbox({focusMode, orientation: 'vertical', wrap: true}); - for (let i = 0; i < optionElements.length - 1; i++) await down(); - await down(); + it('should wrap focus from last to first with ArrowDown when wrap is true (vertical)', () => { + setupListbox({focusMode, orientation: 'vertical', wrap: true}); + for (let i = 0; i < optionElements.length - 1; i++) down(); + down(); expect(isFocused(0)).toBe(true); }); - it('should not wrap focus from last to first with ArrowDown when wrap is false (vertical)', async () => { - await setupListbox({focusMode, orientation: 'vertical', wrap: false}); - for (let i = 0; i < optionElements.length - 1; i++) await down(); - await down(); + it('should not wrap focus from last to first with ArrowDown when wrap is false (vertical)', () => { + setupListbox({focusMode, orientation: 'vertical', wrap: false}); + for (let i = 0; i < optionElements.length - 1; i++) down(); + down(); expect(isFocused(optionElements.length - 1)).toBe(true); }); describe('vertical orientation', () => { - it('should move focus to the next option on ArrowDown', async () => { - await setupListbox({focusMode, orientation: 'vertical'}); - await down(); + it('should move focus to the next option on ArrowDown', () => { + setupListbox({focusMode, orientation: 'vertical'}); + down(); expect(isFocused(1)).toBe(true); }); - it('should skip disabled options with ArrowDown (softDisabled="false")', async () => { - await setupListbox({ + it('should skip disabled options with ArrowDown (softDisabled="false")', () => { + setupListbox({ focusMode, orientation: 'vertical', softDisabled: false, disabledOptions: [1, 2], }); - await down(); + down(); expect(isFocused(3)).toBe(true); }); - it('should not skip disabled options with ArrowDown (softDisabled="true")', async () => { - await setupListbox({ + it('should not skip disabled options with ArrowDown (softDisabled="true")', () => { + setupListbox({ focusMode, orientation: 'vertical', softDisabled: true, disabledOptions: [1, 2], }); - await down(); + down(); expect(isFocused(1)).toBe(true); }); - it('should not be focusable ArrowDown when completely disabled', async () => { - await setupListbox({ + it('should not be focusable ArrowDown when completely disabled', () => { + setupListbox({ focusMode, orientation: 'vertical', softDisabled: true, disabled: true, }); - await down(); + down(); expect(isFocused(0)).toBe(false); }); }); describe('horizontal orientation', () => { - it('should move focus to the next option on ArrowRight', async () => { - await setupListbox({focusMode, orientation: 'horizontal'}); - await right(); + it('should move focus to the next option on ArrowRight', () => { + setupListbox({focusMode, orientation: 'horizontal'}); + right(); expect(isFocused(1)).toBe(true); }); describe('text direction rtl', () => { - it('should move focus to the next option on ArrowLeft (rtl)', async () => { - await setupListbox({focusMode, textDirection: 'rtl', orientation: 'horizontal'}); - await left(); + it('should move focus to the next option on ArrowLeft (rtl)', () => { + setupListbox({focusMode, textDirection: 'rtl', orientation: 'horizontal'}); + left(); expect(isFocused(1)).toBe(true); }); }); @@ -776,26 +672,26 @@ describe('Listbox', () => { }); describe(`pointer navigation (focusMode="${focusMode}")`, () => { - it('should move focus to the clicked option', async () => { - await setupListbox({focusMode}); - await click(3); + it('should move focus to the clicked option', () => { + setupListbox({focusMode}); + click(3); expect(isFocused(3)).toBe(true); }); - it('should move focus to the clicked disabled option', async () => { - await setupListbox({focusMode, disabledOptions: [2], softDisabled: true}); - await click(2); + it('should move focus to the clicked disabled option', () => { + setupListbox({focusMode, disabledOptions: [2], softDisabled: true}); + click(2); expect(isFocused(2)).toBe(true); }); - it('should move focus if listbox is readonly', async () => { - await setupListbox({focusMode, readonly: true}); - await click(3); + it('should move focus if listbox is readonly', () => { + setupListbox({focusMode, readonly: true}); + click(3); expect(isFocused(3)).toBe(true); }); }); - describe(`typeahead functionality (focusMode="${focusMode}")`, () => { + describe('typeahead functionality', () => { const getOptions = () => [ {value: 0, label: 'Apple', disabled: false}, {value: 1, label: 'Apricot', disabled: false}, @@ -804,60 +700,50 @@ describe('Listbox', () => { {value: 4, label: 'Orange', disabled: false}, ]; - it('should focus the first matching option when typing characters', async () => { - await setupListbox({options: getOptions(), focusMode}); - await type('B'); + it('should focus the first matching option when typing characters', () => { + setupListbox({options: getOptions(), focusMode}); + type('B'); expect(isFocused(2)).toBe(true); - await type('l'); + type('l'); expect(isFocused(3)).toBe(true); }); - it('should select the focused option if selectionMode is "follow"', async () => { - await setupListbox({options: getOptions(), focusMode, selectionMode: 'follow'}); - await type('O'); + it('should select the focused option if selectionMode is "follow"', () => { + setupListbox({options: getOptions(), focusMode, selectionMode: 'follow'}); + type('O'); expect(isFocused(4)).toBe(true); - expect(listboxInstance.value()).toEqual([4]); + expect(listboxInstance.values()).toEqual([4]); expect(optionElements[4].getAttribute('aria-selected')).toBe('true'); }); - it('should not select the focused option if selectionMode is "explicit"', async () => { - await setupListbox({options: getOptions(), focusMode, selectionMode: 'explicit'}); - await type('O'); + it('should not select the focused option if selectionMode is "explicit"', () => { + setupListbox({options: getOptions(), focusMode, selectionMode: 'explicit'}); + type('O'); expect(isFocused(4)).toBe(true); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); expect(optionElements[4].getAttribute('aria-selected')).toBe('false'); }); it('should reset search term after typeaheadDelay', async () => { - await setupListbox({options: getOptions(), focusMode, typeaheadDelay: 100}); + setupListbox({options: getOptions(), focusMode, typeaheadDelay: 100}); - await type('A'); + type('A'); expect(isFocused(1)).toBe(true); - await new Promise(resolve => setTimeout(resolve, 200)); + await new Promise(resolve => setTimeout(resolve, 100)); - await type('A'); + type('A'); expect(isFocused(0)).toBe(true); }); - it('should skip disabled options with typeahead (softDisabled=false)', async () => { - await setupListbox({ - options: getOptions(), - focusMode, - disabledOptions: [2], - softDisabled: false, - }); - await type('B'); + it('should skip disabled options with typeahead (softDisabled=false)', () => { + setupListbox({options: getOptions(), focusMode, disabledOptions: [2], softDisabled: false}); + type('B'); expect(isFocused(3)).toBe(true); }); - it('should focus disabled options with typeahead if softDisabled=true', async () => { - await setupListbox({ - options: getOptions(), - focusMode, - disabledOptions: [2], - softDisabled: true, - }); - await type('B'); + it('should focus disabled options with typeahead if softDisabled=true', () => { + setupListbox({options: getOptions(), focusMode, disabledOptions: [2], softDisabled: true}); + type('B'); expect(isFocused(2)).toBe(true); }); }); @@ -876,29 +762,12 @@ describe('Listbox', () => { }); describe('failure cases', () => { - it('should handle an empty set of options gracefully', async () => { - await setupListbox({options: []}); + it('should handle an empty set of options gracefully', () => { + setupListbox({options: []}); expect(optionElements.length).toBe(0); - await down(); - await space(); - expect(listboxInstance.value()).toEqual([]); - }); - }); - - describe('item mutations and focus stability', () => { - it('should recover focus by shifting to the default state if the active option is removed', async () => { - await setupListbox({focusMode: 'activedescendant'}); - await click(2); - expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[2].id); - - const testComponent = fixture.componentInstance as ListboxExample; - const updatedOptions = testComponent.options().filter(o => o.value !== 2); - testComponent.options.set(updatedOptions); - await fixture.whenStable(); - await waitForMicrotasks(); - defineTestVariables(fixture); - - expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[0].id); + expect(() => down()).not.toThrow(); + expect(() => space()).not.toThrow(); + expect(listboxInstance.values()).toEqual([]); }); }); }); @@ -914,7 +783,7 @@ interface TestOption {
        + [typeaheadDelay]="typeaheadDelay"> @for (option of options(); track option.value) {
      • {{ option.label }}
      • }
      `, imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxExample { options = signal([ @@ -942,7 +809,7 @@ class ListboxExample { {value: 4, label: 'Option 4', disabled: false}, ]); - value: number[] = []; + values: number[] = []; disabled = false; readonly = false; softDisabled = true; @@ -952,7 +819,6 @@ class ListboxExample { wrap = true; selectionMode: 'follow' | 'explicit' = 'explicit'; typeaheadDelay = 500; - tabIndex: number | undefined = undefined; } @Component({ @@ -964,42 +830,5 @@ class ListboxExample {
    `, imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.Eager, }) class DefaultListboxExample {} - -@Component({ - template: ` -
      -
    • Item 0
    • -
    • Item 0 Copy
    • -
    - `, - imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ListboxWithDuplicateValues {} - -@Component({ - template: ` -
      -
    • Item 0
    • -
    • Item 1
    • -
    - `, - imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ListboxWithDuplicateIds {} - -@Component({ - template: ` -
      -
    • Item 0
    • -
    • Item 1
    • -
    - `, - imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class SingleSelectListboxWithMultipleValues {} diff --git a/src/aria/listbox/listbox.ts b/src/aria/listbox/listbox.ts index 2ac44999361d..4576d88b3d57 100644 --- a/src/aria/listbox/listbox.ts +++ b/src/aria/listbox/listbox.ts @@ -8,22 +8,21 @@ import { afterRenderEffect, - afterNextRender, booleanAttribute, computed, + contentChildren, Directive, ElementRef, inject, input, model, - OnDestroy, signal, - Signal, untracked, } from '@angular/core'; import {Directionality} from '@angular/cdk/bidi'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {ListboxPattern, SortedCollection, tabIndexTransform, reportViolations} from '../private'; +import {ComboboxListboxPattern, ListboxPattern, OptionPattern} from '../private'; +import {ComboboxPopup} from '../combobox'; import {Option} from './option'; import {LISTBOX} from './tokens'; @@ -44,6 +43,8 @@ import {LISTBOX} from './tokens'; *
* ``` * + * @developerPreview 21.0 + * * @see [Listbox](guide/aria/listbox) * @see [Autocomplete](guide/aria/autocomplete) * @see [Select](guide/aria/select) @@ -55,150 +56,150 @@ import {LISTBOX} from './tokens'; host: { 'role': 'listbox', '[attr.id]': 'id()', - '[attr.tabindex]': 'tabIndex() !== undefined ? tabIndex() : _pattern.tabIndex()', + '[attr.tabindex]': '_pattern.tabIndex()', '[attr.aria-readonly]': '_pattern.readonly()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.aria-orientation]': '_pattern.orientation()', '[attr.aria-multiselectable]': '_pattern.multi()', '[attr.aria-activedescendant]': '_pattern.activeDescendant()', '(keydown)': '_pattern.onKeydown($event)', - '(click)': '_pattern.onClick($event)', - '(focusin)': '_pattern.onFocusIn()', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, + hostDirectives: [ComboboxPopup], providers: [{provide: LISTBOX, useExisting: Listbox}], }) -export class Listbox implements OnDestroy { +export class Listbox { /** A unique identifier for the listbox. */ readonly id = input(inject(_IdGenerator).getId('ng-listbox-', true)); + /** A reference to the parent combobox popup, if one exists. */ + private readonly _popup = inject>(ComboboxPopup, { + optional: true, + }); + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of Options. */ - readonly _collection = new SortedCollection>(); + /** The Options nested inside of the Listbox. */ + private readonly _options = contentChildren(Option, {descendants: true}); /** A signal wrapper for directionality. */ - protected readonly textDirection = inject(Directionality).valueSignal.asReadonly(); + protected textDirection = inject(Directionality).valueSignal.asReadonly(); + + /** The Option UIPatterns of the child Options. */ + protected items = computed[]>(() => + this._options().map(option => option._pattern), + ); /** Whether the list is vertically or horizontally oriented. */ - readonly orientation = input<'vertical' | 'horizontal'>('vertical'); + orientation = input<'vertical' | 'horizontal'>('vertical'); /** Whether multiple items in the list can be selected at once. */ - readonly multi = input(false, {transform: booleanAttribute}); + multi = input(false, {transform: booleanAttribute}); /** Whether focus should wrap when navigating. */ - readonly wrap = input(true, {transform: booleanAttribute}); + wrap = input(true, {transform: booleanAttribute}); /** * Whether to allow disabled items to receive focus. When `true`, disabled items are * focusable but not interactive. When `false`, disabled items are skipped during navigation. */ - readonly softDisabled = input(true, {transform: booleanAttribute}); + softDisabled = input(true, {transform: booleanAttribute}); /** * The focus strategy used by the list. * - `roving`: Focus is moved to the active item using `tabindex`. * - `activedescendant`: Focus remains on the listbox container, and `aria-activedescendant` is used to indicate the active item. */ - readonly focusMode = input<'roving' | 'activedescendant'>('roving'); + focusMode = input<'roving' | 'activedescendant'>('roving'); /** * The selection strategy used by the list. * - `follow`: The focused item is automatically selected. * - `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar). */ - readonly selectionMode = input<'follow' | 'explicit'>('follow'); + selectionMode = input<'follow' | 'explicit'>('follow'); /** The amount of time before the typeahead search is reset. */ - readonly typeaheadDelay = input(500); // Picked arbitrarily. + typeaheadDelay = input(500); // Picked arbitrarily. /** Whether the listbox is disabled. */ - readonly disabled = input(false, {transform: booleanAttribute}); + disabled = input(false, {transform: booleanAttribute}); /** Whether the listbox is readonly. */ - readonly readonly = input(false, {transform: booleanAttribute}); - - /** The tabindex of the listbox. */ - readonly tabIndex = input(undefined, { - alias: 'tabindex', - transform: tabIndexTransform, - }); + readonly = input(false, {transform: booleanAttribute}); /** The values of the currently selected items. */ - readonly value = model([]); + values = model([]); /** The Listbox UIPattern. */ readonly _pattern: ListboxPattern; - /** The ID of the active descendant in the listbox. */ - readonly activeDescendant: Signal; - - private readonly _orderedItemPatterns = computed(() => - this._collection.orderedItems().map(option => option._pattern), - ); + /** Whether the listbox has received focus yet. */ + private _hasFocused = signal(false); constructor() { const inputs = { ...this, id: this.id, - items: this._orderedItemPatterns, + items: this.items, activeItem: signal(undefined), textDirection: this.textDirection, element: () => this._elementRef.nativeElement, + combobox: () => this._popup?.combobox?._pattern, }; - this._pattern = new ListboxPattern(inputs); + this._pattern = this._popup?.combobox + ? new ComboboxListboxPattern(inputs) + : new ListboxPattern(inputs); - this.activeDescendant = computed(() => this._pattern.activeDescendant()); + if (this._popup) { + this._popup._controls.set(this._pattern as ComboboxListboxPattern); + } - afterNextRender(() => { - this._collection.startObserving(this.element); + afterRenderEffect(() => { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + const violations = this._pattern.validate(); + for (const violation of violations) { + console.error(violation); + } + } }); - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); - }, - }); - } - - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); + afterRenderEffect(() => { + if (!this._hasFocused()) { + this._pattern.setDefaultState(); + } + }); // Ensure that if the active item is removed from // the list, the listbox updates it's focus state. - afterRenderEffect({ - write: () => { - const items = inputs.items(); - const activeItem = untracked(() => inputs.activeItem()); - - if (activeItem && !items.some(i => i === activeItem)) { - this._pattern.listBehavior.unfocus(); - this._pattern.setDefaultState(); - } - }, + afterRenderEffect(() => { + const items = inputs.items(); + const activeItem = untracked(() => inputs.activeItem()); + + if (!items.some(i => i === activeItem) && activeItem) { + this._pattern.listBehavior.unfocus(); + } }); - // Ensure that the value is always in sync with the available options. - // This needs to be after the render for the value to always be available. - afterRenderEffect({ - write: () => { - const items = inputs.items(); - const value = untracked(() => this.value()); + // Ensure that the values are always in sync with the available options. + afterRenderEffect(() => { + const items = inputs.items(); + const values = untracked(() => this.values()); - if (items && value.some(v => !items.some(i => i.value() === v))) { - this.value.set(value.filter(v => items.some(i => i.value() === v))); - } - }, + if (items && values.some(v => !items.some(i => i.value() === v))) { + this.values.set(values.filter(v => items.some(i => i.value() === v))); + } }); } - ngOnDestroy() { - this._collection.stopObserving(); + _onFocus() { + this._hasFocused.set(true); } scrollActiveItemIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { @@ -209,14 +210,4 @@ export class Listbox implements OnDestroy { gotoFirst() { this._pattern.listBehavior.first(); } - - /** Navigates to an item at a specific index in the listbox. */ - gotoIndex(index: number) { - const patterns = this._orderedItemPatterns(); - const item = patterns[Math.min(Math.max(index, 0), patterns.length - 1)]; - - if (item) { - this._pattern.listBehavior.goto(item); - } - } } diff --git a/src/aria/listbox/option.ts b/src/aria/listbox/option.ts index fc8bc5a6bee7..b14205678fe9 100644 --- a/src/aria/listbox/option.ts +++ b/src/aria/listbox/option.ts @@ -6,16 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - booleanAttribute, - computed, - Directive, - ElementRef, - inject, - input, - OnInit, - OnDestroy, -} from '@angular/core'; +import {booleanAttribute, computed, Directive, ElementRef, inject, input} from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {OptionPattern} from '../private'; import {LISTBOX} from './tokens'; @@ -33,6 +24,8 @@ import {LISTBOX} from './tokens'; * * ``` * + * @developerPreview 21.0 + * * @see [Listbox](guide/aria/listbox) * @see [Autocomplete](guide/aria/autocomplete) * @see [Select](guide/aria/select) @@ -50,12 +43,12 @@ import {LISTBOX} from './tokens'; '[attr.aria-disabled]': '_pattern.disabled()', }, }) -export class Option implements OnInit, OnDestroy { +export class Option { /** A reference to the host element. */ readonly element = inject(ElementRef).nativeElement as HTMLElement; /** Whether the option is currently active (focused). */ - readonly active = computed(() => this._pattern.active()); + active = computed(() => this._pattern.active()); /** The parent Listbox. */ private readonly _listbox = inject(LISTBOX); @@ -63,17 +56,22 @@ export class Option implements OnInit, OnDestroy { /** A unique identifier for the option. */ readonly id = input(inject(_IdGenerator).getId('ng-option-', true)); + // TODO(wagnermaciel): See if we want to change how we handle this since textContent is not + // reactive. See https://github.com/angular/components/pull/30495#discussion_r1961260216. + /** The text used by the typeahead search. */ + protected searchTerm = computed(() => this.label() ?? this.element.textContent); + /** The parent Listbox UIPattern. */ private readonly _listboxPattern = computed(() => this._listbox._pattern); /** The value of the option. */ - readonly value = input.required(); + value = input.required(); /** Whether an item is disabled. */ - readonly disabled = input(false, {transform: booleanAttribute}); + disabled = input(false, {transform: booleanAttribute}); /** The text used by the typeahead search. */ - readonly label = input(); + label = input(); /** Whether the option is selected. */ readonly selected = computed(() => this._pattern.selected()); @@ -85,14 +83,6 @@ export class Option implements OnInit, OnDestroy { value: this.value, listbox: this._listboxPattern, element: () => this.element, - searchTerm: () => this.label() ?? '', + searchTerm: () => this.searchTerm() ?? '', }); - - ngOnInit() { - this._listbox._collection.register(this); - } - - ngOnDestroy() { - this._listbox._collection.unregister(this); - } } diff --git a/src/aria/listbox/public-api.ts b/src/aria/listbox/public-api.ts index d601e9cbfe33..b91aa4aeb6be 100644 --- a/src/aria/listbox/public-api.ts +++ b/src/aria/listbox/public-api.ts @@ -8,3 +8,13 @@ export {Listbox} from './listbox'; export {Option} from './option'; + +// This needs to be re-exported, because it's used by the listbox components. +// See: https://github.com/angular/components/issues/30663. +export { + Combobox as ɵɵCombobox, + ComboboxDialog as ɵɵComboboxDialog, + ComboboxInput as ɵɵComboboxInput, + ComboboxPopup as ɵɵComboboxPopup, + ComboboxPopupContainer as ɵɵComboboxPopupContainer, +} from '../combobox'; diff --git a/src/aria/listbox/testing/BUILD.bazel b/src/aria/listbox/testing/BUILD.bazel deleted file mode 100644 index b3a3ebb2ab26..000000000000 --- a/src/aria/listbox/testing/BUILD.bazel +++ /dev/null @@ -1,42 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/aria/listbox", - "//src/cdk/testing", - "//src/cdk/testing/private", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [ - ":unit_tests_lib", - ], -) diff --git a/src/aria/listbox/testing/index.ts b/src/aria/listbox/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/listbox/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/listbox/testing/listbox-harness-filters.ts b/src/aria/listbox/testing/listbox-harness-filters.ts deleted file mode 100644 index 5fb667c95a8d..000000000000 --- a/src/aria/listbox/testing/listbox-harness-filters.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** Filters for locating a `ListboxOptionHarness`. */ -export interface ListboxOptionHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose text matches the given value. */ - text?: string | RegExp; - /** Only find instances whose selected state matches the given value. */ - selected?: boolean; - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; -} - -/** Filters for locating a `ListboxHarness`. */ -export interface ListboxHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; -} diff --git a/src/aria/listbox/testing/listbox-harness.spec.ts b/src/aria/listbox/testing/listbox-harness.spec.ts deleted file mode 100644 index 44a77c4d3835..000000000000 --- a/src/aria/listbox/testing/listbox-harness.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; - -import {ListboxHarness, ListboxOptionHarness} from './listbox-harness'; -import {Listbox, Option} from '../index'; - -describe('Listbox Harness', () => { - let fixture: any; - let loader: any; - - @Component({ - imports: [Listbox, Option], - template: ` -
    -
  • Apple
  • -
  • Banana
  • -
    Inside Listbox
    -
- `, - }) - class ListboxHarnessTestComponent {} - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ListboxHarnessTestComponent], - }); - fixture = TestBed.createComponent(ListboxHarnessTestComponent); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('finds the listbox container harness', async () => { - const listbox = await loader.getHarness(ListboxHarness); - expect(listbox).toBeTruthy(); - }); - - it('returns all options scoped within the listbox', async () => { - const listbox = await loader.getHarness(ListboxHarness); - - const options = await listbox.getOptions(); - - expect(options.length).toBe(2); - }); - - it('filters options by exact text content', async () => { - const listbox = await loader.getHarness(ListboxHarness); - - const options = await listbox.getOptions({text: 'Apple'}); - - expect(options.length).toBe(1); - }); - - it('reports the disabled state of the listbox', async () => { - const listbox = await loader.getHarness(ListboxHarness); - - const isDisabled = await listbox.isDisabled(); - - expect(isDisabled).toBeFalse(); - }); - - it('reports the multi-selectable state of the listbox', async () => { - const listbox = await loader.getHarness(ListboxHarness); - - const isMulti = await listbox.isMulti(); - - expect(isMulti).toBeTrue(); - }); - - it('reports the orientation of the listbox', async () => { - const listbox = await loader.getHarness(ListboxHarness); - - const orientation = await listbox.getOrientation(); - - expect(orientation).toBe('horizontal'); - }); - - it('gets the active descendant ID', async () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ListboxActiveDescendantTestComponent], - }); - const customFixture = TestBed.createComponent(ListboxActiveDescendantTestComponent); - customFixture.detectChanges(); - const customLoader = TestbedHarnessEnvironment.loader(customFixture); - - const listbox = await customLoader.getHarness(ListboxHarness); - const options = await listbox.getOptions(); - - await options[0].click(); - expect(await listbox.getActiveDescendantId()).toBe('apple-id'); - }); - - it('clicks an option inside the listbox', async () => { - const option = await loader.getHarness(ListboxOptionHarness.with({text: 'Apple'})); - - await option.click(); - - expect(await option.isSelected()).toBeTrue(); - }); -}); - -@Component({ - imports: [Listbox, Option], - template: ` -
    -
  • Apple
  • -
  • Banana
  • -
- `, -}) -class ListboxActiveDescendantTestComponent {} diff --git a/src/aria/listbox/testing/listbox-harness.ts b/src/aria/listbox/testing/listbox-harness.ts deleted file mode 100644 index 4d9178a881e7..000000000000 --- a/src/aria/listbox/testing/listbox-harness.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; -import {ListboxHarnessFilters, ListboxOptionHarnessFilters} from './listbox-harness-filters'; - -/** Harness for interacting with a standard ngOption in tests. */ -export class ListboxOptionHarness extends ComponentHarness { - static hostSelector = '[ngOption]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for an option - * with specific attributes. - * @param options Options for filtering which option instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: ListboxOptionHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(ListboxOptionHarness, options) - .addOption('text', options.text, (harness, text) => - HarnessPredicate.stringMatches(harness.getText(), text), - ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Whether the option is selected. */ - async isSelected(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-selected')) === 'true'; - } - - /** Whether the option is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return ( - (await host.getAttribute('aria-disabled')) === 'true' || - (await host.getProperty('disabled')) === true - ); - } - - /** Gets the option's text. */ - async getText(): Promise { - const host = await this.host(); - return host.text(); - } - - /** Clicks the option to toggle its selected state. */ - async click(): Promise { - const host = await this.host(); - return host.click(); - } -} - -/** Harness for interacting with a standard ngListbox in tests. */ -export class ListboxHarness extends ComponentHarness { - static hostSelector = '[ngListbox]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a listbox - * with specific attributes. - * @param options Options for filtering which listbox instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: ListboxHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(ListboxHarness, options).addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Gets the orientation of the listbox. */ - async getOrientation(): Promise<'vertical' | 'horizontal'> { - const host = await this.host(); - const orientation = await host.getAttribute('aria-orientation'); - return orientation === 'horizontal' ? 'horizontal' : 'vertical'; - } - - /** Whether the listbox is multiselectable. */ - async isMulti(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-multiselectable')) === 'true'; - } - - /** Whether the listbox is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets the options inside the listbox. */ - async getOptions(filters: ListboxOptionHarnessFilters = {}): Promise { - return this.locatorForAll(ListboxOptionHarness.with(filters))(); - } - - /** Focuses the listbox container. */ - async focus(): Promise { - await (await this.host()).focus(); - } - - /** Blurs the listbox container. */ - async blur(): Promise { - await (await this.host()).blur(); - } - - /** Gets the ID of the active option. */ - async getActiveDescendantId(): Promise { - const host = await this.host(); - return host.getAttribute('aria-activedescendant'); - } -} diff --git a/src/aria/listbox/testing/public-api.ts b/src/aria/listbox/testing/public-api.ts deleted file mode 100644 index 623e2559fd88..000000000000 --- a/src/aria/listbox/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './listbox-harness'; -export * from './listbox-harness-filters'; diff --git a/src/aria/menu/BUILD.bazel b/src/aria/menu/BUILD.bazel index 95aa30742de1..5a70236fb388 100644 --- a/src/aria/menu/BUILD.bazel +++ b/src/aria/menu/BUILD.bazel @@ -25,12 +25,9 @@ ng_project( ), deps = [ ":menu", - "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", "//:node_modules/axe-core", - "//src/aria/private/testing", - "//src/cdk/overlay", "//src/cdk/testing/private", ], ) diff --git a/src/aria/menu/menu-bar.ts b/src/aria/menu/menu-bar.ts index 8c92f1d25996..89e187181cd1 100644 --- a/src/aria/menu/menu-bar.ts +++ b/src/aria/menu/menu-bar.ts @@ -7,20 +7,19 @@ */ import { - afterNextRender, afterRenderEffect, booleanAttribute, computed, + contentChildren, Directive, ElementRef, inject, input, model, - OnDestroy, output, signal, } from '@angular/core'; -import {SignalLike, MenuBarPattern, SortedCollection} from '../private'; +import {SignalLike, MenuBarPattern} from '../private'; import {Directionality} from '@angular/cdk/bidi'; import {MenuItem} from './menu-item'; import {MENU_COMPONENT} from './menu-tokens'; @@ -49,6 +48,8 @@ import {MENU_COMPONENT} from './menu-tokens'; *
* ``` * + * @developerPreview 21.0 + * * @see [Menu](guide/aria/menu) * @see [MenuBar](guide/aria/menubar) */ @@ -68,12 +69,12 @@ import {MENU_COMPONENT} from './menu-tokens'; }, providers: [{provide: MENU_COMPONENT, useExisting: MenuBar}], }) -export class MenuBar implements OnDestroy { - /** The collection of menu items. */ - readonly _collection = new SortedCollection>(); +export class MenuBar { + /** The menu items contained in the menubar. */ + readonly _allItems = contentChildren>(MenuItem, {descendants: true}); readonly _items: SignalLike[]> = () => - this._collection.orderedItems().filter(i => i.parent === this); + this._allItems().filter(i => i.parent === this); /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); @@ -91,7 +92,7 @@ export class MenuBar implements OnDestroy { readonly textDirection = inject(Directionality).valueSignal; /** The values of the currently selected menu items. */ - readonly value = model([]); + readonly values = model([]); /** Whether the menu should wrap its items. */ readonly wrap = input(true, {transform: booleanAttribute}); @@ -103,7 +104,7 @@ export class MenuBar implements OnDestroy { readonly _pattern: MenuBarPattern; /** The menu items as a writable signal. */ - private readonly _itemPatterns = computed(() => this._items().map(i => i._pattern)); + private readonly _itemPatterns = signal([]); /** A callback function triggered when a menu item is selected. */ readonly itemSelected = output(); @@ -113,6 +114,7 @@ export class MenuBar implements OnDestroy { ...this, items: this._itemPatterns, multi: () => false, + softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'horizontal', selectionMode: () => 'explicit', @@ -121,15 +123,15 @@ export class MenuBar implements OnDestroy { element: computed(() => this._elementRef.nativeElement), }); - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); - - afterNextRender(() => { - this._collection.startObserving(this.element); + afterRenderEffect(() => { + this._itemPatterns.set(this._items().map(i => i._pattern)); }); - } - ngOnDestroy() { - this._collection.stopObserving(); + afterRenderEffect(() => { + if (!this._pattern.hasBeenFocused()) { + this._pattern.setDefaultState(); + } + }); } /** Closes the menubar. */ diff --git a/src/aria/menu/menu-content.ts b/src/aria/menu/menu-content.ts index ce4ee1f971b2..fa241646c56c 100644 --- a/src/aria/menu/menu-content.ts +++ b/src/aria/menu/menu-content.ts @@ -24,6 +24,8 @@ import {DeferredContent} from '../private'; *
* ``` * + * @developerPreview 21.0 + * * @see [Menu](guide/aria/menu) * @see [MenuBar](guide/aria/menubar) */ diff --git a/src/aria/menu/menu-item.ts b/src/aria/menu/menu-item.ts index f19e11a25e82..cf0d87d0988b 100644 --- a/src/aria/menu/menu-item.ts +++ b/src/aria/menu/menu-item.ts @@ -6,19 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - computed, - Directive, - effect, - ElementRef, - inject, - input, - model, - OnDestroy, - OnInit, - afterRenderEffect, -} from '@angular/core'; -import {MenuItemPattern, reportViolations} from '../private'; +import {computed, Directive, effect, ElementRef, inject, input, model} from '@angular/core'; +import {MenuItemPattern} from '../private'; import {_IdGenerator} from '@angular/cdk/a11y'; import {MENU_COMPONENT} from './menu-tokens'; import type {Menu} from './menu'; @@ -32,11 +21,13 @@ import type {MenuBar} from './menu-bar'; * * ```html *
- *
Action Item
+ *
Action Item
*
Submenu Trigger
*
* ``` * + * @developerPreview 21.0 + * * @see [Menu](guide/aria/menu) * @see [MenuBar](guide/aria/menubar) */ @@ -44,17 +35,18 @@ import type {MenuBar} from './menu-bar'; selector: '[ngMenuItem]', exportAs: 'ngMenuItem', host: { - '[attr.role]': '_pattern.role()', + 'role': 'menuitem', '(focusin)': '_pattern.onFocusIn()', '[attr.tabindex]': '_pattern.tabIndex()', '[attr.data-active]': 'active()', + '[attr.aria-label]': 'value()', '[attr.aria-haspopup]': 'hasPopup()', '[attr.aria-expanded]': 'expanded()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.aria-controls]': '_pattern.submenu()?.id()', }, }) -export class MenuItem implements OnInit, OnDestroy { +export class MenuItem { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); @@ -64,18 +56,17 @@ export class MenuItem implements OnInit, OnDestroy { /** The unique ID of the menu item. */ readonly id = input(inject(_IdGenerator).getId('ng-menu-item-', true)); - /** The value of the menu item. */ + /** The value of the menu item, used as the default aria-label */ readonly value = input.required(); /** Whether the menu item is disabled. */ readonly disabled = input(false); + // TODO(wagnermaciel): Discuss whether all inputs should be models. + /** The search term associated with the menu item. */ readonly searchTerm = model(''); - /** The role of the menu item. */ - readonly role = input<'menuitem' | 'menuitemradio' | 'menuitemcheckbox'>('menuitem'); - /** A reference to the parent menu or menubar. */ readonly parent = inject | MenuBar>(MENU_COMPONENT, {optional: true}); @@ -100,32 +91,10 @@ export class MenuItem implements OnInit, OnDestroy { searchTerm: this.searchTerm, parent: computed(() => this.parent?._pattern), submenu: computed(() => this.submenu()?._pattern), - role: this.role, }); constructor() { effect(() => this.submenu()?.parent.set(this)); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - if (!this.parent) { - violations.push('ngMenuItem must be placed inside an ngMenu or ngMenuBar container.'); - } - reportViolations(violations, this.element); - }, - }); - } - } - - ngOnInit() { - this.parent?._collection.register(this); - } - - ngOnDestroy() { - this.parent?._collection.unregister(this); } /** Opens the submenu focusing on the first menu item. */ diff --git a/src/aria/menu/menu-trigger.ts b/src/aria/menu/menu-trigger.ts index 7ed57871e656..bf0e7892c9b4 100644 --- a/src/aria/menu/menu-trigger.ts +++ b/src/aria/menu/menu-trigger.ts @@ -35,6 +35,8 @@ import type {Menu} from './menu'; *
* ``` * + * @developerPreview 21.0 + * * @see [Menu](guide/aria/menu) * @see [MenuBar](guide/aria/menubar) */ @@ -65,7 +67,7 @@ export class MenuTrigger { readonly textDirection = inject(Directionality).valueSignal; /** The menu associated with the trigger. */ - readonly menu = input | undefined>(undefined); + menu = input | undefined>(undefined); /** Whether the menu is expanded. */ readonly expanded = computed(() => this._pattern.expanded()); @@ -80,7 +82,7 @@ export class MenuTrigger { readonly softDisabled = input(true, {transform: booleanAttribute}); /** The menu trigger ui pattern instance. */ - readonly _pattern: MenuTriggerPattern = new MenuTriggerPattern({ + _pattern: MenuTriggerPattern = new MenuTriggerPattern({ textDirection: this.textDirection, element: computed(() => this._elementRef.nativeElement), menu: computed(() => this.menu()?._pattern), @@ -89,12 +91,6 @@ export class MenuTrigger { constructor() { effect(() => this.menu()?.parent.set(this)); - effect(() => this._pattern.pendingFocusEffect()); - - // Automatically prevent form submission. - if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { - this.element.setAttribute('type', 'button'); - } } /** Opens the menu focusing on the first menu item. */ diff --git a/src/aria/menu/menu.spec.ts b/src/aria/menu/menu.spec.ts index c95273b1ce00..e97a79243ce5 100644 --- a/src/aria/menu/menu.spec.ts +++ b/src/aria/menu/menu.spec.ts @@ -1,14 +1,4 @@ -import { - Component, - DebugElement, - ChangeDetectionStrategy, - signal, - ViewChild, - inject, - ChangeDetectorRef, -} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {OverlayModule} from '@angular/cdk/overlay'; +import {Component, DebugElement} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {provideFakeDirectionality} from '@angular/cdk/testing/private'; @@ -17,12 +7,11 @@ import {MenuBar} from './menu-bar'; import {MenuContent} from './menu-content'; import {MenuItem} from './menu-item'; import {MenuTrigger} from './menu-trigger'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; describe('Standalone Menu Pattern', () => { let fixture: ComponentFixture; - const keydown = async (element: Element, key: string, modifierKeys: {} = {}) => { + const keydown = (element: Element, key: string, modifierKeys: {} = {}) => { element.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -30,36 +19,36 @@ describe('Standalone Menu Pattern', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; const mouseover = async (element: Element) => { element.dispatchEvent(new MouseEvent('mouseover', {bubbles: true})); await new Promise(resolve => setTimeout(resolve, 0)); - await fixture.whenStable(); + fixture.detectChanges(); }; - const mouseout = async (element: Element) => { + const mouseout = (element: Element) => { element.dispatchEvent(new MouseEvent('mouseout', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (element: Element, eventInit?: PointerEventInit) => { + const click = (element: Element, eventInit?: PointerEventInit) => { element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const focusout = async (element: Element, relatedTarget?: EventTarget) => { + const focusout = (element: Element, relatedTarget?: EventTarget) => { element.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); + fixture.detectChanges(); }; - async function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { + function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { TestBed.configureTestingModule({ providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], }); fixture = TestBed.createComponent(StandaloneMenuExample); - await fixture.whenStable(); + fixture.detectChanges(); getItem('Apple')?.focus(); } @@ -74,169 +63,142 @@ describe('Standalone Menu Pattern', () => { return items.find(item => item.textContent?.trim() === text) || null; } - describe('dynamic updates', () => { - it('should update item order correctly after items are shuffled', async () => { - TestBed.configureTestingModule({imports: [ShuffledMenuExample]}); - const shuffledFixture = TestBed.createComponent(ShuffledMenuExample); - shuffledFixture.detectChanges(); - const menuDirective = shuffledFixture.debugElement - .query(By.directive(Menu)) - .injector.get(Menu); - - const itemsBefore = menuDirective._pattern.inputs.items(); - expect(itemsBefore.length).toBe(3); - expect(itemsBefore[0].element()?.textContent?.trim()).toBe('Apple'); - - // Shuffle items: move first item to the end - const items = (shuffledFixture.componentInstance as unknown as ShuffledMenuExample).items(); - const firstItem = items.shift()!; - items.push(firstItem); - (shuffledFixture.componentInstance as unknown as ShuffledMenuExample).items.set([...items]); - shuffledFixture.detectChanges(); - await waitForMicrotasks(); - - const itemsAfter = menuDirective._pattern.inputs.items(); - expect(itemsAfter.length).toBe(3); - expect(itemsAfter[0].element()?.textContent?.trim()).toBe('Banana'); - }); - }); - describe('Navigation', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); it('should focus the first item by default', () => { expect(getItem('Apple')?.tabIndex).toBe(0); }); - it('should move the active item with the up and down arrows', async () => { + it('should move the active item with the up and down arrows', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); - await keydown(apple!, 'ArrowDown'); + keydown(apple!, 'ArrowDown'); expect(document.activeElement).toBe(banana); - await keydown(banana!, 'ArrowUp'); + keydown(banana!, 'ArrowUp'); expect(document.activeElement).toBe(apple); }); - it('should wrap navigation when reaching the top or bottom', async () => { + it('should wrap navigation when reaching the top or bottom', () => { const apple = getItem('Apple'); const cherry = getItem('Cherry'); - await keydown(apple!, 'ArrowUp'); + keydown(apple!, 'ArrowUp'); expect(document.activeElement).toBe(cherry); - await keydown(cherry!, 'ArrowDown'); + keydown(cherry!, 'ArrowDown'); expect(document.activeElement).toBe(apple); }); - it('should move focus to the first and last items with home and end', async () => { + it('should move focus to the first and last items with home and end', () => { const apple = getItem('Apple'); const cherry = getItem('Cherry'); - await keydown(apple!, 'End'); + keydown(apple!, 'End'); expect(document.activeElement).toBe(cherry); - await keydown(cherry!, 'Home'); + keydown(cherry!, 'Home'); expect(document.activeElement).toBe(apple); }); - it('should move focus on mouse over a menu item', async () => { + it('should move focus on mouse over a menu item', () => { const banana = getItem('Banana'); - await mouseover(banana!); + mouseover(banana!); expect(document.activeElement).toBe(banana); }); describe('Typeahead', () => { - it('should move the active item to the next item that starts with the typed character', async () => { + it('should move the active item to the next item that starts with the typed character', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); - await keydown(apple!, 'b'); + keydown(apple!, 'b'); expect(document.activeElement).toBe(banana); }); - it('should support multi-character typeahead', async () => { + it('should support multi-character typeahead', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'b'); + keydown(apple!, 'b'); expect(document.activeElement).toBe(banana); - await keydown(document.activeElement!, 'e'); + keydown(document.activeElement!, 'e'); expect(document.activeElement).toBe(berries); }); - it('should wrap when reaching the end of the list during typeahead', async () => { + it('should wrap when reaching the end of the list during typeahead', () => { const apple = getItem('Apple'); const cherry = getItem('Cherry'); // Start at cherry by pressing End - await keydown(apple!, 'End'); + keydown(apple!, 'End'); expect(document.activeElement).toBe(cherry); // Type 'a', which should wrap to 'Apple' - await keydown(document.activeElement!, 'a'); + keydown(document.activeElement!, 'a'); expect(document.activeElement).toBe(apple); }); - it('should not move the active item if no item matches the typed character', async () => { + it('should not move the active item if no item matches the typed character', () => { const apple = getItem('Apple'); - await keydown(apple!, 'z'); + keydown(apple!, 'z'); expect(document.activeElement).toBe(apple); }); }); }); describe('Selection', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); - it('should select an item on click', async () => { + it('should select an item on click', () => { const banana = getItem('Banana'); spyOn(fixture.componentInstance, 'itemSelected'); - await click(banana!); + click(banana!); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Banana'); }); - it('should select an item on enter', async () => { + it('should select an item on enter', () => { const banana = getItem('Banana'); spyOn(fixture.componentInstance, 'itemSelected'); - await keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana + keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana expect(document.activeElement).toBe(banana); - await keydown(banana!, 'Enter'); + keydown(banana!, 'Enter'); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Banana'); }); - it('should select an item on space', async () => { + it('should select an item on space', () => { const banana = getItem('Banana'); spyOn(fixture.componentInstance, 'itemSelected'); - await keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana + keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana expect(document.activeElement).toBe(banana); - await keydown(banana!, ' '); + keydown(banana!, ' '); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Banana'); }); - it('should not select a disabled item', async () => { + it('should not select a disabled item', () => { const cherry = getItem('Cherry'); spyOn(fixture.componentInstance, 'itemSelected'); - await click(cherry!); + click(cherry!); expect(fixture.componentInstance.itemSelected).not.toHaveBeenCalled(); - await keydown(document.activeElement!, 'End'); + keydown(document.activeElement!, 'End'); expect(document.activeElement).toBe(cherry); - await keydown(cherry!, 'Enter'); + keydown(cherry!, 'Enter'); expect(fixture.componentInstance.itemSelected).not.toHaveBeenCalled(); - await keydown(cherry!, ' '); + keydown(cherry!, ' '); expect(fixture.componentInstance.itemSelected).not.toHaveBeenCalled(); }); }); @@ -252,97 +214,97 @@ describe('Standalone Menu Pattern', () => { return berries?.getAttribute('aria-expanded') === 'true'; } - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); it('should be expanded by default', () => { expect(getMenu()).not.toBeNull(); expect(isSubmenuExpanded()).toBe(false); }); - it('should expand submenu on click', async () => { + it('should expand submenu on click', () => { const berries = getItem('Berries'); - await click(berries!); + click(berries!); expect(isSubmenuExpanded()).toBe(true); }); - it('should open submenu on arrow right', async () => { + it('should open submenu on arrow right', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowRight'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowRight'); expect(isSubmenuExpanded()).toBe(true); expect(document.activeElement).toBe(getItem('Blueberry')); }); - it('should close submenu on arrow left', async () => { + it('should close submenu on arrow left', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowRight'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowRight'); const blueberry = getItem('Blueberry'); - await keydown(blueberry!, 'ArrowLeft'); + keydown(blueberry!, 'ArrowLeft'); expect(isSubmenuExpanded()).toBe(false); expect(document.activeElement).toBe(berries); }); - it('should close submenu on click outside', async () => { + it('should close submenu on click outside', () => { const berries = getItem('Berries'); - await click(berries!); + click(berries!); expect(isSubmenuExpanded()).toBe(true); - await focusout(getItem('Blueberry')!, document.body); + focusout(getItem('Blueberry')!, document.body); expect(isSubmenuExpanded()).toBe(false); }); - it('should expand submenu on enter', async () => { + it('should expand submenu on enter', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'Enter'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'Enter'); expect(isSubmenuExpanded()).toBe(true); expect(document.activeElement).toBe(getItem('Blueberry')); }); - it('should expand submenu on space', async () => { + it('should expand submenu on space', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, ' '); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, ' '); expect(isSubmenuExpanded()).toBe(true); expect(document.activeElement).toBe(getItem('Blueberry')); }); - it('should close submenu on escape', async () => { + it('should close submenu on escape', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowRight'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowRight'); expect(isSubmenuExpanded()).toBe(true); const blueberry = getItem('Blueberry'); expect(document.activeElement).toBe(blueberry); - await keydown(blueberry!, 'Escape'); + keydown(blueberry!, 'Escape'); expect(isSubmenuExpanded()).toBe(false); expect(document.activeElement).toBe(berries); @@ -354,63 +316,63 @@ describe('Standalone Menu Pattern', () => { expect(isSubmenuExpanded()).toBe(true); }); - it('should close on selecting an item on click', async () => { + it('should close on selecting an item on click', () => { spyOn(fixture.componentInstance, 'itemSelected'); - await click(getItem('Berries')!); // open submenu + click(getItem('Berries')!); // open submenu expect(isSubmenuExpanded()).toBe(true); - await click(getItem('Blueberry')!); + click(getItem('Blueberry')!); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); - it('should close on selecting an item on enter', async () => { + it('should close on selecting an item on enter', () => { spyOn(fixture.componentInstance, 'itemSelected'); const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'Enter'); // open submenu + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'Enter'); // open submenu const blueberry = getItem('Blueberry'); expect(document.activeElement).toBe(blueberry); - await keydown(blueberry!, 'Enter'); + keydown(blueberry!, 'Enter'); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); - it('should close on selecting an item on space', async () => { + it('should close on selecting an item on space', () => { spyOn(fixture.componentInstance, 'itemSelected'); const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, ' '); // open submenu + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, ' '); // open submenu const blueberry = getItem('Blueberry'); expect(document.activeElement).toBe(blueberry); - await keydown(blueberry!, ' '); + keydown(blueberry!, ' '); expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); - it('should close a submenu on focus out', async () => { + it('should close a submenu on focus out', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowRight'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowRight'); expect(isSubmenuExpanded()).toBe(true); const blueberry = getItem('Blueberry'); expect(document.activeElement).toBe(blueberry); @@ -418,7 +380,7 @@ describe('Standalone Menu Pattern', () => { const externalElement = document.createElement('button'); document.body.appendChild(externalElement); - await focusout(blueberry!, externalElement); + focusout(blueberry!, externalElement); expect(isSubmenuExpanded()).toBe(false); externalElement.remove(); @@ -431,8 +393,8 @@ describe('Standalone Menu Pattern', () => { await mouseover(berries!); expect(isSubmenuExpanded()).toBe(true); - await mouseout(berries!); - await mouseout(submenu!); + mouseout(berries!); + mouseout(submenu!); expect(isSubmenuExpanded()).toBe(false); }); @@ -444,8 +406,8 @@ describe('Standalone Menu Pattern', () => { await mouseover(berries!); expect(isSubmenuExpanded()).toBe(true); - await mouseout(berries!); - await mouseover(submenu!); + mouseout(berries!); + mouseover(submenu!); expect(isSubmenuExpanded()).toBe(true); }); }); @@ -456,31 +418,31 @@ describe('Standalone Menu Pattern', () => { return berries?.getAttribute('aria-expanded') === 'true'; } - beforeEach(async () => await setupMenu({textDirection: 'rtl'})); + beforeEach(() => setupMenu({textDirection: 'rtl'})); - it('should open submenu on arrow left', async () => { + it('should open submenu on arrow left', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowLeft'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowLeft'); expect(isSubmenuExpanded()).toBe(true); expect(document.activeElement).toBe(getItem('Blueberry')); }); - it('should close submenu on arrow right', async () => { + it('should close submenu on arrow right', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); - await keydown(apple!, 'ArrowDown'); - await keydown(banana!, 'ArrowDown'); - await keydown(berries!, 'ArrowLeft'); + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowLeft'); const blueberry = getItem('Blueberry'); - await keydown(blueberry!, 'ArrowRight'); + keydown(blueberry!, 'ArrowRight'); expect(isSubmenuExpanded()).toBe(false); expect(document.activeElement).toBe(berries); @@ -488,122 +450,26 @@ describe('Standalone Menu Pattern', () => { }); it('should not reset default state on hover triggers expansion', async () => { + TestBed.configureTestingModule({}); fixture = TestBed.createComponent(StandaloneMenuExample); - await fixture.whenStable(); + fixture.detectChanges(); const berries = getItem('Berries'); await mouseover(berries!); expect(berries?.getAttribute('data-active')).toBe('true'); }); - - it('should be able to set an aria-label on a menu item', async () => { - fixture = TestBed.createComponent(StandaloneMenuExample); - await fixture.whenStable(); - - const item = getItem('Apple'); - expect(item?.getAttribute('aria-label')).toBeFalsy(); - - fixture.componentInstance.firstItemAriaLabel.set('Apple item label'); - await fixture.whenStable(); - expect(item?.getAttribute('aria-label')).toBe('Apple item label'); - }); - - describe('softDisabled', () => { - it('should skip disabled items during navigation when softDisabled is false', async () => { - await setupMenu(); - fixture.componentInstance.softDisabled.set(false); - await fixture.whenStable(); - - const apple = getItem('Apple')!; - const berries = getItem('Berries'); - - await keydown(apple, 'ArrowUp'); - expect(document.activeElement).toBe(berries); - }); - - it('should focus disabled items during navigation when softDisabled is true', async () => { - await setupMenu(); - fixture.componentInstance.softDisabled.set(true); - await fixture.whenStable(); - - const apple = getItem('Apple')!; - const cherry = getItem('Cherry'); - - await keydown(apple!, 'ArrowUp'); - expect(document.activeElement).toBe(cherry); - }); - }); - - describe('role override', () => { - it('should allow overriding the default menuitem role', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [MenuItemRoleOverrideExample], - }); - const roleFixture = TestBed.createComponent(MenuItemRoleOverrideExample); - roleFixture.detectChanges(); - - const items = roleFixture.debugElement - .queryAll(By.directive(MenuItem)) - .map(debugEl => debugEl.nativeElement as HTMLElement); - - expect(items[0].getAttribute('role')).toBe('menuitemradio'); - expect(items[1].getAttribute('role')).toBe('menuitemcheckbox'); - - roleFixture.componentInstance.customRole.set('menuitem'); - roleFixture.detectChanges(); - expect(items[1].getAttribute('role')).toBe('menuitem'); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupMenu(); - }); - - it('should warn when duplicate values are detected inside ngMenu', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [MenuWithDuplicateValues], - }); - const duplicateFixture = TestBed.createComponent(MenuWithDuplicateValues); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith("Duplicate value 'item0' detected inside ngMenu."); - }); - - it('should warn when ngMenuItem is outside ngMenu or ngMenuBar', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [MenuItemOutsideMenu], - }); - const noMenuFixture = TestBed.createComponent(MenuItemOutsideMenu); - noMenuFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngMenuItem must be placed inside an ngMenu or ngMenuBar container.', - ); - }); - }); }); describe('Menu Trigger Pattern', () => { let fixture: ComponentFixture; - const focusin = async (element: Element) => { + const focusin = (element: Element) => { element.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const keydown = async (element: Element, key: string, modifierKeys: {} = {}) => { - await focusin(element); + const keydown = (element: Element, key: string, modifierKeys: {} = {}) => { + focusin(element); element.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -611,23 +477,23 @@ describe('Menu Trigger Pattern', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (element: Element, eventInit?: PointerEventInit) => { - await focusin(element); + const click = (element: Element, eventInit?: PointerEventInit) => { + focusin(element); element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const focusout = async (element: Element, relatedTarget?: EventTarget) => { + const focusout = (element: Element, relatedTarget?: EventTarget) => { element.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); + fixture.detectChanges(); }; - async function setupMenu() { + function setupMenu() { fixture = TestBed.createComponent(MenuTriggerExample); - await fixture.whenStable(); + fixture.detectChanges(); } function getTrigger(): HTMLElement { @@ -651,226 +517,121 @@ describe('Menu Trigger Pattern', () => { } describe('Navigation', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); - it('should navigate to the first item on click', async () => { - await click(getTrigger()); + it('should navigate to the first item on click', () => { + click(getTrigger()); expect(document.activeElement).toBe(getItem('Apple')); }); - it('should navigate to the first item on arrow down', async () => { + it('should navigate to the first item on arrow down', () => { const trigger = getTrigger(); - await keydown(trigger, 'ArrowDown'); + keydown(trigger, 'ArrowDown'); expect(document.activeElement).toBe(getItem('Apple')); }); - it('should navigate to the last item on arrow up', async () => { + it('should navigate to the last item on arrow up', () => { const trigger = getTrigger(); - await keydown(trigger, 'ArrowUp'); + keydown(trigger, 'ArrowUp'); expect(document.activeElement).toBe(getItem('Cherry')); }); - it('should navigate to the first item on enter', async () => { + it('should navigate to the first item on enter', () => { const trigger = getTrigger(); - await keydown(trigger, 'Enter'); + keydown(trigger, 'Enter'); expect(document.activeElement).toBe(getItem('Apple')); }); - it('should navigate to the first item on space', async () => { + it('should navigate to the first item on space', () => { const trigger = getTrigger(); - await keydown(trigger, ' '); + keydown(trigger, ' '); expect(document.activeElement).toBe(getItem('Apple')); }); }); describe('Expansion', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); it('should be closed by default', () => { expect(isExpanded()).toBe(false); }); - it('should open on click', async () => { - await click(getTrigger()); + it('should open on click', () => { + click(getTrigger()); expect(isExpanded()).toBe(true); }); - it('should close on second click', async () => { + it('should close on second click', () => { const trigger = getTrigger(); - await click(trigger); + click(trigger); expect(isExpanded()).toBe(true); - await click(trigger); + click(trigger); expect(isExpanded()).toBe(false); }); - it('should open on arrow down', async () => { - await keydown(getTrigger(), 'ArrowDown'); + it('should open on arrow down', () => { + keydown(getTrigger(), 'ArrowDown'); expect(isExpanded()).toBe(true); }); - it('should open on arrow up', async () => { - await keydown(getTrigger(), 'ArrowUp'); + it('should open on arrow up', () => { + keydown(getTrigger(), 'ArrowUp'); expect(isExpanded()).toBe(true); }); - it('should open on space', async () => { - await keydown(getTrigger(), ' '); + it('should open on space', () => { + keydown(getTrigger(), ' '); expect(isExpanded()).toBe(true); }); - it('should open on enter', async () => { - await keydown(getTrigger(), 'Enter'); + it('should open on enter', () => { + keydown(getTrigger(), 'Enter'); expect(isExpanded()).toBe(true); }); - it('should close on escape', async () => { + it('should close on escape', () => { const trigger = getTrigger(); - await click(trigger); + click(trigger); expect(isExpanded()).toBe(true); - await keydown(getMenu()!, 'Escape'); + keydown(getMenu()!, 'Escape'); expect(isExpanded()).toBe(false); expect(document.activeElement).toBe(trigger); }); - it('should close on selecting an item on click', async () => { - await click(getTrigger()); - await click(getItem('Apple')!); + it('should close on selecting an item on click', () => { + click(getTrigger()); + click(getItem('Apple')!); expect(isExpanded()).toBe(false); }); - it('should close on selecting an item on enter', async () => { - await click(getTrigger()); - await keydown(getItem('Apple')!, 'Enter'); + it('should close on selecting an item on enter', () => { + click(getTrigger()); + keydown(getItem('Apple')!, 'Enter'); expect(isExpanded()).toBe(false); }); it('should close on selecting an item on space', async () => { - await click(getTrigger()); - await keydown(getItem('Apple')!, ' '); + click(getTrigger()); + keydown(getItem('Apple')!, ' '); expect(isExpanded()).toBe(false); }); - it('should close the trigger on focus out from the menu', async () => { - await click(getTrigger()); + it('should close the trigger on focus out from the menu', () => { + click(getTrigger()); expect(isExpanded()).toBe(true); - await focusout(getMenu()!, document.body); + focusout(getMenu()!, document.body); expect(isExpanded()).toBe(false); }); }); - - describe('Selection', () => { - beforeEach(async () => await setupMenu()); - - it('should select an item on click', async () => { - spyOn(fixture.componentInstance, 'itemSelected'); - await click(getTrigger()); - await click(getItem('Apple')!); - expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Apple'); - }); - }); -}); - -describe('CDK Overlay Menu Pattern', () => { - let fixture: ComponentFixture; - - const focusin = async (element: Element) => { - element.dispatchEvent(new FocusEvent('focusin', {bubbles: true})); - await fixture.whenStable(); - }; - - const keydown = async (element: Element, key: string, modifierKeys: {} = {}) => { - await focusin(element); - element.dispatchEvent( - new KeyboardEvent('keydown', { - key, - bubbles: true, - ...modifierKeys, - }), - ); - await fixture.whenStable(); - await waitForMicrotasks(); - await fixture.whenStable(); - }; - - const click = async (element: Element, eventInit?: PointerEventInit) => { - await focusin(element); - element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); - await waitForMicrotasks(); - await fixture.whenStable(); - }; - - async function setupMenu() { - fixture = TestBed.createComponent(CdkOverlayMenuExample); - await fixture.whenStable(); - } - - function getTrigger(): HTMLElement { - return fixture.debugElement.query(By.directive(MenuTrigger)).nativeElement as HTMLElement; - } - - function getItem(text: string): HTMLElement | null { - const items = fixture.debugElement - .queryAll(By.directive(MenuItem)) - .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); - return items.find(item => item.textContent?.trim() === text) || null; - } - - beforeEach(async () => await setupMenu()); - - it('should focus the first item when opened via arrow down', async () => { - await keydown(getTrigger(), 'ArrowDown'); - expect(document.activeElement).toBe(getItem('Apple')); - }); - - it('should focus the first item when opened via enter', async () => { - await keydown(getTrigger(), 'Enter'); - expect(document.activeElement).toBe(getItem('Apple')); - }); - - it('should focus the first item when opened via space', async () => { - await keydown(getTrigger(), ' '); - expect(document.activeElement).toBe(getItem('Apple')); - }); - - it('should focus the first item when opened via click', async () => { - await click(getTrigger()); - expect(document.activeElement).toBe(getItem('Apple')); - }); - - it('should focus the first item stably when opened, closed via escape, and opened again', async () => { - const trigger = getTrigger(); - - // First open - await keydown(trigger, 'Enter'); - expect(document.activeElement).toBe(getItem('Apple')); - - // Close via escape - await keydown(getItem('Apple')!, 'Escape'); - expect(trigger.getAttribute('aria-expanded')).toBe('false'); - expect(document.activeElement).toBe(trigger); - - // Explicitly clear cached menu before second open - fixture.componentInstance.clearMenu(); - await fixture.whenStable(); - - // Second open - await keydown(trigger, 'Enter'); - expect(document.activeElement).toBe(getItem('Apple')); - }); - - it('should set type="button" by default on button triggers', () => { - expect(getTrigger().getAttribute('type')).toBe('button'); - }); }); describe('Menu Bar Pattern', () => { let fixture: ComponentFixture; - const keydown = async (element: Element, key: string, modifierKeys: {} = {}) => { + const keydown = (element: Element, key: string, modifierKeys: {} = {}) => { element.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -878,30 +639,30 @@ describe('Menu Bar Pattern', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const mouseover = async (element: Element) => { + const mouseover = (element: Element) => { element.dispatchEvent(new MouseEvent('mouseover', {bubbles: true})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (element: Element, eventInit?: PointerEventInit) => { + const click = (element: Element, eventInit?: PointerEventInit) => { element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); - await fixture.whenStable(); + fixture.detectChanges(); }; - const focusout = async (element: Element, relatedTarget?: EventTarget) => { + const focusout = (element: Element, relatedTarget?: EventTarget) => { element.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget})); - await fixture.whenStable(); + fixture.detectChanges(); }; - async function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { + function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { TestBed.configureTestingModule({ providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], }); fixture = TestBed.createComponent(MenuBarExample); - await fixture.whenStable(); + fixture.detectChanges(); getMenuBarItem('File')?.focus(); } @@ -930,298 +691,267 @@ describe('Menu Bar Pattern', () => { return getMenuBarItem(menuBarItemText)?.getAttribute('aria-expanded') === 'true'; } - describe('dynamic updates', () => { - it('should update item order correctly after items are shuffled', async () => { - TestBed.configureTestingModule({imports: [ShuffledMenuBarExample]}); - const shuffledFixture = TestBed.createComponent(ShuffledMenuBarExample); - shuffledFixture.detectChanges(); - const menuBarDirective = shuffledFixture.debugElement - .query(By.directive(MenuBar)) - .injector.get(MenuBar); - - const itemsBefore = menuBarDirective._pattern.inputs.items(); - expect(itemsBefore.length).toBe(3); - expect(itemsBefore[0].element()?.textContent?.trim()).toBe('File'); - - // Shuffle items: move first item to the end - const items = ( - shuffledFixture.componentInstance as unknown as ShuffledMenuBarExample - ).items(); - const firstItem = items.shift()!; - items.push(firstItem); - (shuffledFixture.componentInstance as unknown as ShuffledMenuBarExample).items.set([ - ...items, - ]); - shuffledFixture.detectChanges(); - await waitForMicrotasks(); - - const itemsAfter = menuBarDirective._pattern.inputs.items(); - expect(itemsAfter.length).toBe(3); - expect(itemsAfter[0].element()?.textContent?.trim()).toBe('Edit'); - }); - }); - describe('Navigation', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); - it('should navigate to the first item on arrow down', async () => { + it('should navigate to the first item on arrow down', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowDown'); expect(document.activeElement).toBe(getMenuItem('Zoom In')); }); - it('should navigate to the last item on arrow up', async () => { + it('should navigate to the last item on arrow up', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowUp'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowUp'); expect(document.activeElement).toBe(getMenuItem('Full Screen')); }); - it('should navigate to the first item on enter', async () => { + it('should navigate to the first item on enter', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'Enter'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'Enter'); expect(document.activeElement).toBe(getMenuItem('Zoom In')); }); - it('should navigate to the first item on space', async () => { + it('should navigate to the first item on space', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, ' '); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, ' '); expect(document.activeElement).toBe(getMenuItem('Zoom In')); }); - it('should navigate to a menubar item on mouse over', async () => { + it('should navigate to a menubar item on mouse over', () => { const edit = getMenuBarItem('Edit'); - await mouseover(edit!); + mouseover(edit!); expect(document.activeElement).toBe(edit); }); - it('should focus the first item of the next menubar item on arrow right', async () => { + it('should focus the first item of the next menubar item on arrow right', () => { const edit = getMenuBarItem('Edit'); const file = getMenuBarItem('File'); const view = getMenuBarItem('View'); const documentation = getMenuBarItem('Documentation'); const zoomIn = getMenuItem('Zoom In'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowDown'); - await keydown(zoomIn!, 'ArrowRight'); + keydown(zoomIn!, 'ArrowRight'); expect(document.activeElement).toBe(documentation); }); - it('should focus the first item of the previous menubar item on arrow left', async () => { + it('should focus the first item of the previous menubar item on arrow left', () => { const edit = getMenuBarItem('Edit'); const file = getMenuBarItem('File'); const view = getMenuBarItem('View'); const undo = getMenuItem('Undo'); const zoomIn = getMenuItem('Zoom In'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowDown'); - await keydown(zoomIn!, 'ArrowLeft'); + keydown(zoomIn!, 'ArrowLeft'); expect(document.activeElement).toBe(undo); }); }); describe('Expansion', () => { - beforeEach(async () => await setupMenu()); + beforeEach(() => setupMenu()); it('should be collapsed by default', () => { expect(isExpanded('View')).toBe(false); }); - it('should expand on click', async () => { - await click(getMenuBarItem('View')!); + it('should expand on click', () => { + click(getMenuBarItem('View')!); expect(isExpanded('View')).toBe(true); }); - it('should collapse on second click', async () => { + it('should collapse on second click', () => { const view = getMenuBarItem('View'); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(true); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(false); }); - it('should expand on arrow down', async () => { + it('should expand on arrow down', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowDown'); expect(isExpanded('View')).toBe(true); }); - it('should expand on arrow up', async () => { + it('should expand on arrow up', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'ArrowUp'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'ArrowUp'); expect(isExpanded('View')).toBe(true); }); - it('should expand on space', async () => { + it('should expand on space', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, ' '); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, ' '); expect(isExpanded('View')).toBe(true); }); - it('should expand on enter', async () => { + it('should expand on enter', () => { const file = getMenuBarItem('File'); const edit = getMenuBarItem('Edit'); const view = getMenuBarItem('View'); - await keydown(file!, 'ArrowRight'); - await keydown(edit!, 'ArrowRight'); - await keydown(view!, 'Enter'); + keydown(file!, 'ArrowRight'); + keydown(edit!, 'ArrowRight'); + keydown(view!, 'Enter'); expect(isExpanded('View')).toBe(true); }); - it('should close on escape', async () => { + it('should close on escape', () => { const view = getMenuBarItem('View'); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(true); - await keydown(getMenu()!, 'Escape'); + keydown(getMenu()!, 'Escape'); expect(isExpanded('View')).toBe(false); expect(document.activeElement).toBe(view); }); - it('should close on selecting an item on click', async () => { - await click(getMenuBarItem('View')!); - await click(getMenuItem('Zoom In')!); + it('should close on selecting an item on click', () => { + click(getMenuBarItem('View')!); + click(getMenuItem('Zoom In')!); expect(isExpanded('View')).toBe(false); }); - it('should close on selecting an item on enter', async () => { + it('should close on selecting an item on enter', () => { const view = getMenuBarItem('View'); - await click(view!); - await keydown(view!, 'ArrowDown'); - await keydown(getMenuItem('Zoom In')!, 'Enter'); + click(view!); + keydown(view!, 'ArrowDown'); + keydown(getMenuItem('Zoom In')!, 'Enter'); expect(isExpanded('View')).toBe(false); }); - it('should close on selecting an item on space', async () => { + it('should close on selecting an item on space', () => { const view = getMenuBarItem('View'); - await click(view!); - await keydown(view!, 'ArrowDown'); - await keydown(getMenuItem('Zoom In')!, ' '); + click(view!); + keydown(view!, 'ArrowDown'); + keydown(getMenuItem('Zoom In')!, ' '); expect(isExpanded('View')).toBe(false); }); - it('should close on focus out from the menu', async () => { - await click(getMenuBarItem('View')!); + it('should close on focus out from the menu', () => { + click(getMenuBarItem('View')!); expect(isExpanded('View')).toBe(true); - await focusout(getMenu()!, document.body); + focusout(getMenu()!, document.body); expect(isExpanded('View')).toBe(false); }); - it('should close on arrow right on a leaf menu item', async () => { + it('should close on arrow right on a leaf menu item', () => { const view = getMenuBarItem('View'); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(true); - await keydown(getMenuItem('Zoom In')!, 'ArrowRight'); + keydown(getMenuItem('Zoom In')!, 'ArrowRight'); expect(isExpanded('View')).toBe(false); }); - it('should close on arrow left on a root menu item', async () => { + it('should close on arrow left on a root menu item', () => { const view = getMenuBarItem('View'); - await click(view!); - await keydown(view!, 'ArrowDown'); - await keydown(getMenuItem('Zoom In')!, 'ArrowLeft'); + click(view!); + keydown(view!, 'ArrowDown'); + keydown(getMenuItem('Zoom In')!, 'ArrowLeft'); expect(isExpanded('View')).toBe(false); }); - it('should expand the next menu bar item on arrow right on a leaf menu item', async () => { + it('should expand the next menu bar item on arrow right on a leaf menu item', () => { const view = getMenuBarItem('View'); const zoomIn = getMenuItem('Zoom In'); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(true); expect(document.activeElement).toBe(view); - await keydown(view!, 'ArrowDown'); + keydown(view!, 'ArrowDown'); expect(document.activeElement).toBe(zoomIn); - await keydown(zoomIn!, 'ArrowRight'); + keydown(zoomIn!, 'ArrowRight'); expect(isExpanded('View')).toBe(false); expect(isExpanded('Help')).toBe(true); }); - it('should expand the previous menu bar item on arrow left on a root menu item', async () => { + it('should expand the previous menu bar item on arrow left on a root menu item', () => { const view = getMenuBarItem('View'); const zoomIn = getMenuItem('Zoom In'); - await click(view!); + click(view!); expect(isExpanded('View')).toBe(true); expect(document.activeElement).toBe(view); - await keydown(view!, 'ArrowDown'); + keydown(view!, 'ArrowDown'); expect(document.activeElement).toBe(zoomIn); - await keydown(zoomIn!, 'ArrowLeft'); + keydown(zoomIn!, 'ArrowLeft'); expect(isExpanded('View')).toBe(false); expect(isExpanded('Edit')).toBe(true); }); }); describe('RTL', () => { - beforeEach(async () => await setupMenu({textDirection: 'rtl'})); + beforeEach(() => setupMenu({textDirection: 'rtl'})); - it('should focus the first item of the next menubar item on arrow left', async () => { + it('should focus the first item of the next menubar item on arrow left', () => { const edit = getMenuBarItem('Edit'); const file = getMenuBarItem('File'); const view = getMenuBarItem('View'); const documentation = getMenuBarItem('Documentation'); const zoomIn = getMenuItem('Zoom In'); - await keydown(file!, 'ArrowLeft'); - await keydown(edit!, 'ArrowLeft'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowLeft'); + keydown(edit!, 'ArrowLeft'); + keydown(view!, 'ArrowDown'); - await keydown(zoomIn!, 'ArrowLeft'); + keydown(zoomIn!, 'ArrowLeft'); expect(document.activeElement).toBe(documentation); }); - it('should focus the first item of the previous menubar item on arrow right', async () => { + it('should focus the first item of the previous menubar item on arrow right', () => { const edit = getMenuBarItem('Edit'); const file = getMenuBarItem('File'); const view = getMenuBarItem('View'); const undo = getMenuItem('Undo'); const zoomIn = getMenuItem('Zoom In'); - await keydown(file!, 'ArrowLeft'); - await keydown(edit!, 'ArrowLeft'); - await keydown(view!, 'ArrowDown'); + keydown(file!, 'ArrowLeft'); + keydown(edit!, 'ArrowLeft'); + keydown(view!, 'ArrowDown'); - await keydown(zoomIn!, 'ArrowRight'); + keydown(zoomIn!, 'ArrowRight'); expect(document.activeElement).toBe(undo); }); }); @@ -1229,218 +959,89 @@ describe('Menu Bar Pattern', () => { @Component({ template: ` -
+
+ +
Apple
+
Banana
+
Berries
+ +
-
Apple
-
Banana
-
Berries
- -
- -
Blueberry
-
Blackberry
-
Strawberry
-
-
- -
Cherry
+
Blueberry
+
Blackberry
+
Strawberry
+ +
Cherry
+
+
`, imports: [Menu, MenuItem, MenuContent], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneMenuExample { - firstItemAriaLabel = signal(null); - softDisabled = signal(true); - itemSelected(value: string) {} } @Component({ template: ` - + -
+
+ +
Apple
+
Banana
+
Berries
+ +
-
Apple
-
Banana
-
Berries
- -
- -
Blueberry
-
Blackberry
-
Strawberry
-
-
- -
Cherry
+
Blueberry
+
Blackberry
+
Strawberry
- `, - imports: [Menu, MenuItem, MenuTrigger, MenuContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class MenuTriggerExample { - itemSelected(value: string) {} -} - -@Component({ - template: ` -
-
File
-
Edit
- -
- -
Undo
-
Redo
-
-
- -
View
- -
- -
Zoom In
-
Zoom Out
-
Full Screen
-
-
- -
Help
- -
- -
Documentation
-
About
-
-
-
- `, - imports: [Menu, MenuBar, MenuItem, MenuContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class MenuBarExample {} - -@Component({ - template: ` -
- @for (item of items(); track item) { -
{{item.value}}
- } -
- `, - imports: [Menu, MenuItem], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ShuffledMenuExample { - items = signal([{value: 'Apple'}, {value: 'Banana'}, {value: 'Cherry'}]); -} -@Component({ - template: ` -
- @for (item of items(); track item) { -
{{item.value}}
- } -
+
Cherry
+
+
`, - imports: [MenuBar, MenuItem], - changeDetection: ChangeDetectionStrategy.Eager, + imports: [Menu, MenuItem, MenuTrigger, MenuContent], }) -class ShuffledMenuBarExample { - items = signal([{value: 'File'}, {value: 'Edit'}, {value: 'View'}]); -} +class MenuTriggerExample {} @Component({ template: ` -
- -
Item 0
-
Item 0 Copy
-
-
- `, - imports: [Menu, MenuItem, MenuContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class MenuWithDuplicateValues {} +
+
File
+
Edit
+ +
+ +
Undo
+
Redo
+
+
-@Component({ - template: ` -
Item 0
- `, - imports: [MenuItem], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class MenuItemOutsideMenu {} +
View
-@Component({ - template: ` - - - - - - -
- -
Apple
-
Banana
-
-
-
+
+ +
Zoom In
+
Zoom Out
+
Full Screen
- `, - imports: [CommonModule, OverlayModule, Menu, MenuTrigger, MenuItem, MenuContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class CdkOverlayMenuExample { - @ViewChild('overlayMenu') _myMenu!: Menu; - private _cachedMenu?: Menu; - private readonly _cdr = inject(ChangeDetectorRef); - - get myMenu() { - if (this._myMenu) { - this._cachedMenu = this._myMenu; - } - return this._cachedMenu; - } +
- clearMenu() { - this._cachedMenu = undefined; - this._cdr.markForCheck(); - } -} +
Help
-@Component({ - template: ` -
-
Item 0
-
Item 1
-
+
+ +
Documentation
+
About
+
+
+
`, - imports: [Menu, MenuItem], - changeDetection: ChangeDetectionStrategy.Eager, + imports: [Menu, MenuBar, MenuItem, MenuContent], }) -class MenuItemRoleOverrideExample { - customRole = signal<'menuitem' | 'menuitemradio' | 'menuitemcheckbox'>('menuitemcheckbox'); -} +class MenuBarExample {} diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index a4b79e76a307..ff16ad6078cd 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -7,21 +7,20 @@ */ import { - afterNextRender, afterRenderEffect, booleanAttribute, computed, + contentChildren, Directive, ElementRef, inject, input, - OnDestroy, output, Signal, signal, untracked, } from '@angular/core'; -import {MenuPattern, DeferredContentAware, SortedCollection, reportViolations} from '../private'; +import {MenuPattern, DeferredContentAware} from '../private'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import {MenuTrigger} from './menu-trigger'; @@ -51,6 +50,8 @@ import {MENU_COMPONENT} from './menu-tokens'; *
* ``` * + * @developerPreview 21.0 + * * @see [Menu](guide/aria/menu) * @see [MenuBar](guide/aria/menubar) */ @@ -78,16 +79,16 @@ import {MENU_COMPONENT} from './menu-tokens'; ], providers: [{provide: MENU_COMPONENT, useExisting: Menu}], }) -export class Menu implements OnDestroy { +export class Menu { /** The DeferredContentAware host directive. */ private readonly _deferredContentAware = inject(DeferredContentAware, {optional: true}); - /** The collection of menu items. */ - readonly _collection = new SortedCollection>(); + /** The menu items contained in the menu. */ + readonly _allItems = contentChildren>(MenuItem, {descendants: true}); /** The menu items that are direct children of this menu. */ readonly _items: Signal[]> = computed(() => - this._collection.orderedItems().filter(i => i.parent === this), + this._allItems().filter(i => i.parent === this), ); /** A reference to the host element. */ @@ -114,28 +115,17 @@ export class Menu implements OnDestroy { /** A reference to the parent menu item or menu trigger. */ readonly parent = signal | MenuItem | undefined>(undefined); - /** Whether the menu is soft disabled. */ - readonly softDisabled = input(true, {transform: booleanAttribute}); - /** The menu ui pattern instance. */ readonly _pattern: MenuPattern; /** - * The menu item patterns for the menu items that are direct children of this menu, passed - * to the menu pattern. + * The menu items as a writable signal. * - * Note: contentChildren has an issue where it will return a successively smaller list - * each time that the menu is open and closed, eventually resulting in an empty list. - * The workaround is to trigger a recomputation of this signal whenever the menu is opened - * or closed, by calling this._pattern.visible() in the signal body. Otherwise, computed could - * not be used and would have to rebuild the list each time this method is called. + * TODO(wagnermaciel): This would normally be a computed, but using a computed causes a bug where + * sometimes the items array is empty. The bug can be reproduced by switching this to use a + * computed and then quickly opening and closing menus in the dev app. */ - private readonly _itemPatterns = computed(() => { - // Only needed to force a recompute. - this._pattern.visible(); - - return this._items().map(i => i._pattern); - }); + private readonly _itemPatterns = () => this._items().map(i => i._pattern); /** Whether the menu is visible. */ readonly visible = computed(() => this._pattern.visible()); @@ -155,6 +145,7 @@ export class Menu implements OnDestroy { parent: computed(() => this.parent()?._pattern), items: this._itemPatterns, multi: () => false, + softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'vertical', selectionMode: () => 'explicit', @@ -163,51 +154,39 @@ export class Menu implements OnDestroy { itemSelected: (value: V) => this.itemSelected.emit(value), }); - afterRenderEffect({ - write: () => { - const parent = this.parent(); - if (parent instanceof MenuItem && parent.parent instanceof MenuBar) { - this._deferredContentAware?.contentVisible.set(true); - } else { - this._deferredContentAware?.contentVisible.set( - this._pattern.visible() || !!this.parent()?._pattern.hasBeenInteracted(), - ); - } - }, + afterRenderEffect(() => { + const parent = this.parent(); + if (parent instanceof MenuItem && parent.parent instanceof MenuBar) { + this._deferredContentAware?.contentVisible.set(true); + } else { + this._deferredContentAware?.contentVisible.set( + this._pattern.visible() || !!this.parent()?._pattern.hasBeenFocused(), + ); + } }); - // Focuses an active menu item when the menu becomes visible. This is needed to - // properly restore focus to the active item when returning to a menu, and to - // focus the first item when navigating into a submenu with hover. - afterRenderEffect({ - write: () => { - if (this.visible()) { - const activeItem = untracked(() => this._pattern.inputs.activeItem()); - this._pattern.listBehavior.goto(activeItem!); - } - }, + // TODO(wagnermaciel): This is a redundancy needed for if the user uses display: none to hide + // submenus. In those cases, the ui pattern is calling focus() before the ui has a chance to + // update the display property. The result is focus() being called on an element that is not + // focusable. This simply retries focusing the element after render. + afterRenderEffect(() => { + if (this._pattern.visible()) { + const activeItem = untracked(() => this._pattern.inputs.activeItem()); + this._pattern.listBehavior.goto(activeItem!); + } }); - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); - }, - }); - } - - afterNextRender(() => { - this._collection.startObserving(this.element); + afterRenderEffect(() => { + if ( + !this._pattern.hasBeenFocused() && + !this._pattern.hasBeenHovered() && + this._items().length + ) { + untracked(() => this._pattern.setDefaultState()); + } }); } - ngOnDestroy() { - this._collection.stopObserving(); - } - /** Closes the menu. */ close() { this._pattern.close(); diff --git a/src/aria/menu/testing/BUILD.bazel b/src/aria/menu/testing/BUILD.bazel deleted file mode 100644 index c20fd6664983..000000000000 --- a/src/aria/menu/testing/BUILD.bazel +++ /dev/null @@ -1,40 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/aria/menu", - "//src/cdk/testing", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [ - ":unit_tests_lib", - ], -) diff --git a/src/aria/menu/testing/index.ts b/src/aria/menu/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/menu/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/menu/testing/menu-harness-filters.ts b/src/aria/menu/testing/menu-harness-filters.ts deleted file mode 100644 index 7700e9f03480..000000000000 --- a/src/aria/menu/testing/menu-harness-filters.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** Filters for locating a `MenuHarness`. */ -export interface MenuHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose trigger text matches the given value. */ - triggerText?: string | RegExp; -} - -/** Filters for locating a `MenuItemHarness`. */ -export interface MenuItemHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose text matches the given value. */ - text?: string | RegExp; - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; - /** Only find instances whose expanded state matches the given value. */ - expanded?: boolean; -} diff --git a/src/aria/menu/testing/menu-harness.spec.ts b/src/aria/menu/testing/menu-harness.spec.ts deleted file mode 100644 index 3bc42181e7f4..000000000000 --- a/src/aria/menu/testing/menu-harness.spec.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Menu} from '../menu'; -import {MenuContent} from '../menu-content'; -import {MenuItem} from '../menu-item'; -import {MenuTrigger} from '../menu-trigger'; -import {MenuBar} from '../menu-bar'; -import {MenuItemHarness, MenuHarness} from './menu-harness'; - -describe('Aria Menu Harness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - fixture = TestBed.createComponent(MenuTestApp); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should locate the menu harness', async () => { - await expectAsync( - loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})), - ).toBeResolved(); - }); - - it('should verify that the menu is initially closed', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - expect(await menu.isOpen()).toBe(false); - }); - - it('should open the menu', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - fixture.detectChanges(); - - expect(await menu.isOpen()).toBe(true); - }); - - it('should close the menu', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - fixture.detectChanges(); - - await menu.close(); - fixture.detectChanges(); - expect(await menu.isOpen()).toBe(false); - }); - - it('should get all items inside an open menu', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - fixture.detectChanges(); - - const items = await menu.getItems(); - expect(items.length).toBe(3); - expect(await items[0].getText()).toBe('Item 1'); - }); - - it('should filter menu items by their disabled state', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - fixture.detectChanges(); - - const disabledItems = await loader.getAllHarnesses(MenuItemHarness.with({disabled: true})); - expect(disabledItems.length).toBe(1); - expect(await disabledItems[0].getText()).toBe('Item 2'); - }); - - it('should locate and interact with nested submenus', async () => { - const main = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await main.open(); - fixture.detectChanges(); - - const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'})); - await subItem.click(); - fixture.detectChanges(); - - const submenu = await subItem.getSubmenu(); - expect(await submenu!.isOpen()).toBe(true); - }); - - it('should read items within a nested submenu', async () => { - const main = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await main.open(); - fixture.detectChanges(); - - const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'})); - await subItem.click(); - fixture.detectChanges(); - - const submenu = await subItem.getSubmenu(); - const subItems = await submenu!.getItems(); - expect(subItems.length).toBe(1); - expect(await subItems[0].getText()).toBe('Nested Item'); - }); - - it('should confirm persistent horizontal menu bars are always open', async () => { - const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'})); - expect(await menubar.isOpen()).toBe(true); - }); - - it('should read items from a persistent horizontal menu bar', async () => { - const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'})); - const items = await menubar.getItems(); - - expect(items.length).toBe(2); - expect(await items[0].getText()).toBe('File'); - expect(await items[1].getText()).toBe('Edit'); - }); - - it('should be able to get whether a menu item is focused', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - const items = await menu.getItems(); - - const itemHost = await items[0].host(); - await itemHost.focus(); - fixture.detectChanges(); - - expect(await items[0].isFocused()).toBe(true); - expect(await items[1].isFocused()).toBe(false); - }); - - it('should be able to get whether a menu item has a submenu', async () => { - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - await menu.open(); - const items = await menu.getItems(); - - expect(await items[0].hasSubmenu()).toBe(false); - expect(await items[2].hasSubmenu()).toBe(true); - }); - - it('should be able to get whether a menu is a menu bar', async () => { - const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'})); - expect(await menubar.isMenuBar()).toBe(true); - - const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); - expect(await menu.isMenuBar()).toBe(false); - }); -}); - -@Component({ - template: ` - - -
- -
Item 1
-
Item 2
-
Submenu
-
-
- -
- -
Nested Item
-
-
- -
-
File
-
Edit
-
- `, - imports: [Menu, MenuItem, MenuTrigger, MenuBar, MenuContent], -}) -class MenuTestApp {} diff --git a/src/aria/menu/testing/menu-harness.ts b/src/aria/menu/testing/menu-harness.ts deleted file mode 100644 index 059522461e0e..000000000000 --- a/src/aria/menu/testing/menu-harness.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ComponentHarness, HarnessPredicate, TestElement} from '@angular/cdk/testing'; -import {MenuHarnessFilters, MenuItemHarnessFilters} from './menu-harness-filters'; - -/** Harness for interacting with a standard ngMenuItem in tests. */ -export class MenuItemHarness extends ComponentHarness { - static hostSelector = '[ngMenuItem]'; - - static with(options: MenuItemHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(MenuItemHarness, options) - .addOption('text', options.text, (harness, text) => - HarnessPredicate.stringMatches(harness.getText(), text), - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ) - .addOption( - 'expanded', - options.expanded, - async (harness, expanded) => (await harness.isExpanded()) === expanded, - ); - } - - /** Gets the text content of the menu item. */ - async getText(): Promise { - return (await this.host()).text(); - } - - /** Whether the menu item is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Whether the menu item is expanded (contains an open submenu). */ - async isExpanded(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-expanded')) === 'true'; - } - - /** Clicks the menu item to trigger its action or toggle its submenu. */ - async click(): Promise { - return (await this.host()).click(); - } - - /** Resolves the nested submenu panel associated with this menu item, if any exists. */ - async getSubmenu(): Promise { - const controlsId = await (await this.host()).getAttribute('aria-controls'); - if (controlsId) { - return this.documentRootLocatorFactory().locatorFor( - MenuHarness.with({selector: `#${controlsId}`}), - )(); - } - return null; - } - - /** Whether the menu item has focus. */ - async isFocused(): Promise { - return (await this.host()).isFocused(); - } - - /** Whether the menu item acts as a submenu trigger. */ - async hasSubmenu(): Promise { - const host = await this.host(); - return ( - (await host.getAttribute('aria-haspopup')) === 'true' || - !!(await host.getAttribute('aria-controls')) - ); - } -} - -/** Harness for interacting with a standard ngMenu or ngMenuBar in tests. */ -export class MenuHarness extends ComponentHarness { - static hostSelector = '[ngMenu], [ngMenuBar]'; - - static with(options: MenuHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(MenuHarness, options).addOption( - 'triggerText', - options.triggerText, - async (harness, text) => { - const trigger = await harness._getTrigger(); - if (!trigger) return false; - return HarnessPredicate.stringMatches(await trigger.text(), text); - }, - ); - } - - /** Resolves the trigger associated with this menu container via aria-controls inversion. */ - async _getTrigger(): Promise { - const id = await (await this.host()).getAttribute('id'); - if (!id) return null; - return this.documentRootLocatorFactory().locatorForOptional(`[aria-controls="${id}"]`)(); - } - - /** Checks whether the menu container is visible. */ - async isOpen(): Promise { - const host = await this.host(); - // Menu bars are always visible persistently. - if (await host.matchesSelector('[ngMenuBar]')) { - return true; - } - return (await host.getAttribute('data-visible')) === 'true'; - } - - /** Whether the menu is a menu bar. */ - async isMenuBar(): Promise { - const host = await this.host(); - return host.matchesSelector('[ngMenuBar]'); - } - - /** Opens the menu if it is currently closed. */ - async open(): Promise { - if (!(await this.isOpen())) { - const trigger = await this._getTrigger(); - if (trigger) { - await trigger.click(); - } - } - } - - /** Closes the menu if it is currently open. */ - async close(): Promise { - if (await this.isOpen()) { - const trigger = await this._getTrigger(); - if (trigger) { - await trigger.click(); - } - } - } - - /** Queries all menu items inside this menu container. */ - async getItems(filters: MenuItemHarnessFilters = {}): Promise { - return this.locatorForAll(MenuItemHarness.with(filters))(); - } -} diff --git a/src/aria/menu/testing/public-api.ts b/src/aria/menu/testing/public-api.ts deleted file mode 100644 index 2a408c036830..000000000000 --- a/src/aria/menu/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './menu-harness'; -export * from './menu-harness-filters'; diff --git a/src/aria/private/BUILD.bazel b/src/aria/private/BUILD.bazel index 72eac82d2380..f688ab1b20e1 100644 --- a/src/aria/private/BUILD.bazel +++ b/src/aria/private/BUILD.bazel @@ -1,19 +1,12 @@ -load("//tools:defaults.bzl", "ng_web_test_suite", "ts_project") +load("//tools:defaults.bzl", "ts_project") package(default_visibility = ["//visibility:public"]) -ng_web_test_suite( - name = "unit_tests", - deps = ["//src/aria/private/utils:unit_test_sources"], -) - ts_project( name = "private", srcs = glob( ["**/*.ts"], - exclude = [ - "**/*.spec.ts", - ], + exclude = ["**/*.spec.ts"], ), deps = [ "//:node_modules/@angular/core", @@ -27,6 +20,5 @@ ts_project( "//src/aria/private/tabs", "//src/aria/private/toolbar", "//src/aria/private/tree", - "//src/aria/private/utils", ], ) diff --git a/src/aria/private/accordion/accordion.spec.ts b/src/aria/private/accordion/accordion.spec.ts index 610654b64486..8364a21c70bc 100644 --- a/src/aria/private/accordion/accordion.spec.ts +++ b/src/aria/private/accordion/accordion.spec.ts @@ -9,6 +9,8 @@ import { AccordionGroupInputs, AccordionGroupPattern, + AccordionPanelInputs, + AccordionPanelPattern, AccordionTriggerInputs, AccordionTriggerPattern, } from './accordion'; @@ -27,6 +29,8 @@ type TestAccordionGroupInputs = AccordionGroupInputs & WritableSignalOverrides; type TestAccordionTriggerInputs = AccordionTriggerInputs & WritableSignalOverrides; +type TestAccordionPanelInputs = AccordionPanelInputs & + WritableSignalOverrides; const up = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 38, 'ArrowUp', mods); const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); @@ -48,6 +52,8 @@ describe('Accordion Pattern', () => { let groupPattern: AccordionGroupPattern; let triggerInputs: TestAccordionTriggerInputs[]; let triggerPatterns: AccordionTriggerPattern[]; + let panelInputs: TestAccordionPanelInputs[]; + let panelPatterns: AccordionPanelPattern[]; beforeEach(() => { // Initiate AccordionGroupPattern. @@ -61,6 +67,7 @@ describe('Accordion Pattern', () => { softDisabled: signal(true), wrap: signal(true), element: signal(document.createElement('div')), + getItem: e => triggerPatterns.find(i => i.element() === e), }; groupPattern = new AccordionGroupPattern(groupInputs); @@ -68,26 +75,29 @@ describe('Accordion Pattern', () => { triggerInputs = [ { accordionGroup: signal(groupPattern), - accordionPanelId: signal('panel-1-id'), + accordionPanel: signal(undefined), id: signal('trigger-1-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), + panelId: signal('panel-1'), // Value should match the panel it controls expanded: signal(false), }, { accordionGroup: signal(groupPattern), - accordionPanelId: signal('panel-2-id'), + accordionPanel: signal(undefined), id: signal('trigger-2-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), + panelId: signal('panel-2'), expanded: signal(false), }, { accordionGroup: signal(groupPattern), - accordionPanelId: signal('panel-3-id'), + accordionPanel: signal(undefined), id: signal('trigger-3-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), + panelId: signal('panel-3'), expanded: signal(false), }, ]; @@ -97,13 +107,48 @@ describe('Accordion Pattern', () => { new AccordionTriggerPattern(triggerInputs[2]), ]; - groupInputs.items.set(triggerPatterns); groupPattern.inputs.activeItem.set(triggerPatterns[0]); + + // Initiate a list of AccordionPanelPattern. + panelInputs = [ + { + id: signal('panel-1-id'), + panelId: signal('panel-1'), + accordionTrigger: signal(undefined), + }, + { + id: signal('panel-2-id'), + panelId: signal('panel-2'), + accordionTrigger: signal(undefined), + }, + { + id: signal('panel-3-id'), + panelId: signal('panel-3'), + accordionTrigger: signal(undefined), + }, + ]; + panelPatterns = [ + new AccordionPanelPattern(panelInputs[0]), + new AccordionPanelPattern(panelInputs[1]), + new AccordionPanelPattern(panelInputs[2]), + ]; + + // Binding between triggers and panels. + triggerInputs[0].accordionPanel.set(panelPatterns[0]); + triggerInputs[1].accordionPanel.set(panelPatterns[1]); + triggerInputs[2].accordionPanel.set(panelPatterns[2]); + panelInputs[0].accordionTrigger.set(triggerPatterns[0]); + panelInputs[1].accordionTrigger.set(triggerPatterns[1]); + panelInputs[2].accordionTrigger.set(triggerPatterns[2]); + groupInputs.items.set(triggerPatterns); }); it('gets a controlled panel id from a trigger.', () => { + expect(panelPatterns[0].id()).toBe('panel-1-id'); expect(triggerPatterns[0].controls()).toBe('panel-1-id'); + expect(panelPatterns[1].id()).toBe('panel-2-id'); expect(triggerPatterns[1].controls()).toBe('panel-2-id'); + expect(panelPatterns[2].id()).toBe('panel-3-id'); expect(triggerPatterns[2].controls()).toBe('panel-3-id'); }); @@ -112,14 +157,14 @@ describe('Accordion Pattern', () => { groupInputs.disabled.set(true); groupInputs.activeItem.set(triggerPatterns[0]); groupPattern.onKeydown(space()); - expect(triggerPatterns[0].expanded()).toBeFalse(); + expect(panelPatterns[0].hidden()).toBeTrue(); }); it('does not handle keyboard event if an accordion trigger is disabled.', () => { triggerInputs[0].disabled.set(true); groupInputs.activeItem.set(triggerPatterns[0]); groupPattern.onKeydown(space()); - expect(triggerPatterns[0].expanded()).toBeFalse(); + expect(panelPatterns[0].hidden()).toBeTrue(); }); it('navigates to first accordion trigger with home key.', () => { @@ -277,22 +322,22 @@ describe('Accordion Pattern', () => { it('expands a panel and collapses others with space key.', () => { triggerPatterns[1].expanded.set(true); - expect(triggerPatterns[0].expanded()).toBeFalse(); - expect(triggerPatterns[1].expanded()).toBeTrue(); + expect(panelPatterns[0].hidden()).toBeTrue(); + expect(panelPatterns[1].hidden()).toBeFalse(); groupPattern.onKeydown(space()); - expect(triggerPatterns[0].expanded()).toBeTrue(); - expect(triggerPatterns[1].expanded()).toBeFalse(); + expect(panelPatterns[0].hidden()).toBeFalse(); + expect(panelPatterns[1].hidden()).toBeTrue(); }); it('expands a panel and collapses others with enter key.', () => { triggerPatterns[1].expanded.set(true); - expect(triggerPatterns[0].expanded()).toBeFalse(); - expect(triggerPatterns[1].expanded()).toBeTrue(); + expect(panelPatterns[0].hidden()).toBeTrue(); + expect(panelPatterns[1].hidden()).toBeFalse(); - groupPattern.onKeydown(enter()); - expect(triggerPatterns[0].expanded()).toBeTrue(); - expect(triggerPatterns[1].expanded()).toBeFalse(); + groupPattern.onKeydown(space()); + expect(panelPatterns[0].hidden()).toBeFalse(); + expect(panelPatterns[1].hidden()).toBeTrue(); }); }); @@ -303,67 +348,23 @@ describe('Accordion Pattern', () => { it('expands a panel without affecting other panels.', () => { triggerPatterns[1].expanded.set(true); - expect(triggerPatterns[0].expanded()).toBeFalse(); - expect(triggerPatterns[1].expanded()).toBeTrue(); + expect(panelPatterns[0].hidden()).toBeTrue(); + expect(panelPatterns[1].hidden()).toBeFalse(); - groupPattern.onKeydown(enter()); - expect(triggerPatterns[0].expanded()).toBeTrue(); - expect(triggerPatterns[1].expanded()).toBeTrue(); + groupPattern.onKeydown(space()); + expect(panelPatterns[0].hidden()).toBeFalse(); + expect(panelPatterns[1].hidden()).toBeFalse(); }); it('collapses a panel without affecting other panels.', () => { triggerPatterns[0].expanded.set(true); triggerPatterns[1].expanded.set(true); - expect(triggerPatterns[0].expanded()).toBeTrue(); - expect(triggerPatterns[1].expanded()).toBeTrue(); + expect(panelPatterns[0].hidden()).toBeFalse(); + expect(panelPatterns[1].hidden()).toBeFalse(); groupPattern.onKeydown(enter()); - expect(triggerPatterns[0].expanded()).toBeFalse(); - expect(triggerPatterns[1].expanded()).toBeTrue(); - }); - }); - - describe('AccordionTriggerPattern methods', () => { - it('should expand via open()', () => { - expect(triggerPatterns[0].expanded()).toBeFalse(); - triggerPatterns[0].open(); - expect(triggerPatterns[0].expanded()).toBeTrue(); - }); - - it('should collapse via close()', () => { - triggerPatterns[0].expanded.set(true); - expect(triggerPatterns[0].expanded()).toBeTrue(); - triggerPatterns[0].close(); - expect(triggerPatterns[0].expanded()).toBeFalse(); - }); - - it('should toggle via toggle()', () => { - expect(triggerPatterns[0].expanded()).toBeFalse(); - triggerPatterns[0].toggle(); - expect(triggerPatterns[0].expanded()).toBeTrue(); - - triggerPatterns[0].toggle(); - expect(triggerPatterns[0].expanded()).toBeFalse(); - }); - }); - - describe('softDisabled behavior', () => { - it('should compute hardDisabled as true when disabled=true and softDisabled=false', () => { - triggerInputs[0].disabled.set(true); - groupInputs.softDisabled.set(false); - expect(triggerPatterns[0].hardDisabled()).toBeTrue(); - }); - - it('should compute hardDisabled as false when disabled=true and softDisabled=true', () => { - triggerInputs[0].disabled.set(true); - groupInputs.softDisabled.set(true); - expect(triggerPatterns[0].hardDisabled()).toBeFalse(); - }); - - it('should compute hardDisabled as false when disabled=false', () => { - triggerInputs[0].disabled.set(false); - groupInputs.softDisabled.set(false); - expect(triggerPatterns[0].hardDisabled()).toBeFalse(); + expect(panelPatterns[0].hidden()).toBeTrue(); + expect(panelPatterns[1].hidden()).toBeFalse(); }); }); }); diff --git a/src/aria/private/accordion/accordion.ts b/src/aria/private/accordion/accordion.ts index c3db0cecd4d1..76f52123e92e 100644 --- a/src/aria/private/accordion/accordion.ts +++ b/src/aria/private/accordion/accordion.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {KeyboardEventManager, ClickEventManager} from '../behaviors/event-manager'; +import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; import {ExpansionItem, ListExpansion, ListExpansionInputs} from '../behaviors/expansion/expansion'; import {ListFocus, ListFocusInputs, ListFocusItem} from '../behaviors/list-focus/list-focus'; import { @@ -22,7 +22,10 @@ export interface AccordionGroupInputs extends Omit< ListFocusInputs & Omit, 'focusMode' -> {} +> { + /** A function that returns the trigger associated with a given element. */ + getItem: (e: Element | null | undefined) => AccordionTriggerPattern | undefined; +} const focusMode = () => 'roving' as const; @@ -53,7 +56,7 @@ export class AccordionGroupPattern { } /** The key used to navigate to the previous accordion trigger. */ - readonly prevKey = computed(() => { + prevKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowUp'; } @@ -61,7 +64,7 @@ export class AccordionGroupPattern { }); /** The key used to navigate to the next accordion trigger. */ - readonly nextKey = computed(() => { + nextKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowDown'; } @@ -69,7 +72,7 @@ export class AccordionGroupPattern { }); /** The keydown event manager for the accordion trigger. */ - readonly keydown = computed(() => { + keydown = computed(() => { return new KeyboardEventManager() .on(this.prevKey, () => this.navigationBehavior.prev(), {ignoreRepeat: false}) .on(this.nextKey, () => this.navigationBehavior.next(), {ignoreRepeat: false}) @@ -79,10 +82,10 @@ export class AccordionGroupPattern { .on('Enter', () => this.toggle()); }); - /** The click event manager for the accordion trigger. */ - readonly click = computed(() => { - return new ClickEventManager().on((e: PointerEvent) => { - const item = this._findTriggerPattern(e.target as Element); + /** The pointerdown event manager for the accordion trigger. */ + pointerdown = computed(() => { + return new PointerEventManager().on(e => { + const item = this.inputs.getItem(e.target as Element); if (!item) return; this.navigationBehavior.goto(item); @@ -95,14 +98,14 @@ export class AccordionGroupPattern { this.keydown().handle(event); } - /** Handles click events on the trigger, delegating to the group if not disabled. */ - onClick(event: PointerEvent): void { - this.click().handle(event); + /** Handles pointerdown events on the trigger, delegating to the group if not disabled. */ + onPointerdown(event: PointerEvent): void { + this.pointerdown().handle(event); } /** Handles focus events on the trigger. This ensures the tabbing changes the active index. */ onFocus(event: FocusEvent): void { - const item = this._findTriggerPattern(event.target as Element); + const item = this.inputs.getItem(event.target as Element); if (!item) return; if (!this.focusBehavior.isFocusable(item)) return; @@ -115,66 +118,25 @@ export class AccordionGroupPattern { if (activeItem === undefined) return; this.expansionBehavior.toggle(activeItem); } - - /** Expands all accordion panels if multi-expandable. */ - expandAll() { - this.expansionBehavior.openAll(); - } - - /** Collapses all accordion panels. */ - collapseAll() { - this.expansionBehavior.closeAll(); - } - - /** Returns a set of violations */ - validate(): string[] { - const violations: string[] = []; - - if (!this.inputs.multiExpandable()) { - const expandedCount = this.inputs.items().filter(t => t.expanded()).length; - if (expandedCount > 1) { - violations.push( - 'ngAccordionGroup has multiExpandable set to false, but multiple ngAccordionTrigger panels are initially expanded.', - ); - } - } - - return violations; - } - - /** Finds the trigger pattern for a given element. */ - private _findTriggerPattern( - element: Element | null | undefined, - ): AccordionTriggerPattern | undefined { - let target = element; - - while (target) { - const pattern = this.inputs.items().find(t => t.element() === target); - if (pattern) { - return pattern; - } - - target = target.parentElement?.closest('[ngAccordionTrigger]'); - } - - return undefined; - } } /** Inputs for the AccordionTriggerPattern. */ export interface AccordionTriggerInputs extends Omit, Omit { + /** A local unique identifier for the trigger's corresponding panel. */ + panelId: SignalLike; + /** The parent accordion group that controls this trigger. */ accordionGroup: SignalLike; - /** The accordion panel id controlled by this trigger. */ - accordionPanelId: SignalLike; + /** The accordion panel controlled by this trigger. */ + accordionPanel: SignalLike; } /** A pattern controls the expansion state of an accordion. */ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusItem, ExpansionItem { /** A unique identifier for this trigger. */ - readonly id: SignalLike; // set from inputs + readonly id: SignalLike = () => this.inputs.id(); /** A reference to the trigger element. */ readonly element: SignalLike = () => this.inputs.element()!; @@ -183,13 +145,13 @@ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusIte readonly expandable: SignalLike = () => true; /** Whether the corresponding panel is expanded. */ - readonly expanded: WritableSignalLike; // set from inputs + readonly expanded: WritableSignalLike; /** Whether the trigger is active. */ readonly active = computed(() => this.inputs.accordionGroup().inputs.activeItem() === this); /** Id of the accordion panel controlled by the trigger. */ - readonly controls: SignalLike; // set from inputs + readonly controls = computed(() => this.inputs.accordionPanel()?.inputs.id()); /** The tabindex of the trigger. */ readonly tabIndex = computed(() => @@ -206,10 +168,11 @@ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusIte () => this.disabled() && !this.inputs.accordionGroup().inputs.softDisabled(), ); + /** The index of the trigger within its accordion group. */ + readonly index = computed(() => this.inputs.accordionGroup().inputs.items().indexOf(this)); + constructor(readonly inputs: AccordionTriggerInputs) { - this.id = inputs.id; this.expanded = inputs.expanded; - this.controls = inputs.accordionPanelId; } /** Opens the accordion panel. */ @@ -227,3 +190,33 @@ export class AccordionTriggerPattern implements ListNavigationItem, ListFocusIte this.inputs.accordionGroup().expansionBehavior.toggle(this); } } + +/** Represents the required inputs for the AccordionPanelPattern. */ +export interface AccordionPanelInputs { + /** A global unique identifier for the panel. */ + id: SignalLike; + + /** A local unique identifier for the panel, matching its trigger's panelId. */ + panelId: SignalLike; + + /** The parent accordion trigger that controls this panel. */ + accordionTrigger: SignalLike; +} + +/** Represents an accordion panel. */ +export class AccordionPanelPattern { + /** A global unique identifier for the panel. */ + id: SignalLike; + + /** The parent accordion trigger that controls this panel. */ + accordionTrigger: SignalLike; + + /** Whether the accordion panel is hidden. True if the associated trigger is not expanded. */ + hidden: SignalLike; + + constructor(readonly inputs: AccordionPanelInputs) { + this.id = inputs.id; + this.accordionTrigger = inputs.accordionTrigger; + this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false); + } +} diff --git a/src/aria/private/behaviors/event-manager/click-event-manager.ts b/src/aria/private/behaviors/event-manager/click-event-manager.ts deleted file mode 100644 index 3be9949a84e9..000000000000 --- a/src/aria/private/behaviors/event-manager/click-event-manager.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - EventHandler, - EventHandlerOptions, - EventManager, - hasModifiers, - ModifierInputs, - Modifier, -} from './event-manager'; - -/** - * Gets whether an event could be a simulated click event. - * - * Screen readers and keyboard activation (Enter/Space) often dispatch fake click - * events. We distinguish them by checking if `event.detail` is zero or if - * `event.pointerType` is missing. - */ -export function isFakeClick(event: PointerEvent): boolean { - return event.detail === 0 || !event.pointerType; -} - -/** - * Gets whether an event is a programmatic click (e.g. triggered by .click() or .dispatchEvent()). - * Programmatic events are untrusted. - */ -export function isProgrammaticClick(event: Event): boolean { - return !event.isTrusted; -} - -/** - * An event manager that is specialized for handling click events. - * - * This manager should ONLY be used to handle click events. It explicitly - * filters out simulated click events generated by browsers when Enter or Space - * keys are pressed, to avoid concurrent logic or overwriting selection state - * when handling keyboard activation explicitly via KeyboardEventManager. - */ -export class ClickEventManager extends EventManager { - readonly options: EventHandlerOptions = { - preventDefault: false, - stopPropagation: false, - }; - - /** - * Configures this event manager to handle events with a specific modifier combination. - */ - on(modifiers: ModifierInputs, handler: EventHandler): this; - - /** - * Configures this event manager to handle events with no modifiers. - * - * @param handler The handler function - */ - on(handler: EventHandler): this; - - on(...args: unknown[]) { - const {handler, modifiers} = this._normalizeInputs(...args); - - this.configs.push({ - handler, - matcher: event => this._isMatch(event, modifiers), - ...this.options, - }); - return this; - } - - private _normalizeInputs(...args: unknown[]) { - if (args.length === 2) { - return { - modifiers: args[0] as ModifierInputs, - handler: args[1] as EventHandler, - }; - } - - return { - modifiers: Modifier.None, - handler: args[0] as EventHandler, - }; - } - - _isMatch(event: T, modifiers: ModifierInputs) { - const isAllowed = isProgrammaticClick(event) || !isFakeClick(event); - return isAllowed && hasModifiers(event, modifiers); - } -} diff --git a/src/aria/private/behaviors/event-manager/index.ts b/src/aria/private/behaviors/event-manager/index.ts index 8166ce102dfe..d396497d2d16 100644 --- a/src/aria/private/behaviors/event-manager/index.ts +++ b/src/aria/private/behaviors/event-manager/index.ts @@ -9,4 +9,3 @@ export {Modifier} from './event-manager'; export {KeyboardEventManager} from './keyboard-event-manager'; export {PointerEventManager} from './pointer-event-manager'; -export {ClickEventManager} from './click-event-manager'; diff --git a/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts index 0b41a08c8f8d..19f89782e91b 100644 --- a/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts +++ b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts @@ -29,7 +29,7 @@ type KeyCode = string | SignalLike | RegExp; * propagation and prevents default on all events it handles. */ export class KeyboardEventManager extends EventManager { - readonly options: EventHandlerOptions = { + options: EventHandlerOptions = { ignoreRepeat: true, preventDefault: true, stopPropagation: true, @@ -46,7 +46,7 @@ export class KeyboardEventManager extends EventManager< options?: Partial, ): this; - on(...args: unknown[]) { + on(...args: any[]) { const {modifiers, key, handler, options} = this._normalizeInputs(...args); this.configs.push({ @@ -59,8 +59,8 @@ export class KeyboardEventManager extends EventManager< return this; } - private _normalizeInputs(...args: unknown[]) { - const withModifiers = Array.isArray(args[0]) || Modifier.hasOwnProperty(args[0] as string); + private _normalizeInputs(...args: any[]) { + const withModifiers = Array.isArray(args[0]) || args[0] in Modifier; const modifiers = withModifiers ? args[0] : Modifier.None; const key = withModifiers ? args[1] : args[0]; const handler = withModifiers ? args[2] : args[1]; @@ -80,8 +80,7 @@ export class KeyboardEventManager extends EventManager< modifiers: ModifierInputs, options?: Partial, ): boolean { - // In some cases the `key` may be undefined, despite the types saying otherwise. See #33359. - if (event.key == null || !hasModifiers(event, modifiers)) { + if (!hasModifiers(event, modifiers)) { return false; } diff --git a/src/aria/private/behaviors/event-manager/pointer-event-manager.ts b/src/aria/private/behaviors/event-manager/pointer-event-manager.ts index 311bd2c815c0..018cbcc2e92e 100644 --- a/src/aria/private/behaviors/event-manager/pointer-event-manager.ts +++ b/src/aria/private/behaviors/event-manager/pointer-event-manager.ts @@ -26,7 +26,7 @@ export enum MouseButton { /** An event manager that is specialized for handling pointer events. */ export class PointerEventManager extends EventManager { - readonly options: EventHandlerOptions = { + options: EventHandlerOptions = { preventDefault: false, stopPropagation: false, }; diff --git a/src/aria/private/utils/BUILD.bazel b/src/aria/private/behaviors/grid-focus/BUILD.bazel similarity index 53% rename from src/aria/private/utils/BUILD.bazel rename to src/aria/private/behaviors/grid-focus/BUILD.bazel index 67b663d83222..eb4f60a59a92 100644 --- a/src/aria/private/utils/BUILD.bazel +++ b/src/aria/private/behaviors/grid-focus/BUILD.bazel @@ -3,31 +3,22 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") package(default_visibility = ["//visibility:public"]) ts_project( - name = "utils", - srcs = [ - "collection.ts", - "element.ts", - "element-resolver.ts", - "transforms.ts", - "violations.ts", - ], + name = "grid-focus", + srcs = ["grid-focus.ts"], deps = [ "//:node_modules/@angular/core", + "//src/aria/private/behaviors/signal-like", ], ) ng_project( name = "unit_test_sources", testonly = True, - srcs = [ - "collection.spec.ts", - "element-resolver.spec.ts", - ], + srcs = ["grid-focus.spec.ts"], deps = [ - ":utils", + ":grid-focus", "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/aria/private/testing", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/private/behaviors/grid-focus/grid-focus.spec.ts b/src/aria/private/behaviors/grid-focus/grid-focus.spec.ts new file mode 100644 index 000000000000..3fc7c3141b88 --- /dev/null +++ b/src/aria/private/behaviors/grid-focus/grid-focus.spec.ts @@ -0,0 +1,443 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, SignalLike, signal, WritableSignalLike} from '../signal-like/signal-like'; +import {GridFocus, GridFocusInputs, GridFocusCell, RowCol} from './grid-focus'; + +// Helper type for test cells, extending GridFocusCell +interface TestGridCell extends GridFocusCell { + id: WritableSignalLike; + element: WritableSignalLike; + disabled: WritableSignalLike; +} + +// Helper type for configuring GridFocus inputs in tests +type TestSetupInputs = Partial> & { + numRows?: number; + numCols?: number; + gridFocus?: WritableSignalLike | undefined>; +}; + +export function createTestCell( + gridFocus: SignalLike | undefined>, + opts: {id: string; rowspan?: number; colspan?: number}, +): TestGridCell { + const el = document.createElement('div'); + spyOn(el, 'focus').and.callThrough(); + let coordinates: SignalLike = signal({row: -1, col: -1}); + const cell: TestGridCell = { + id: signal(opts.id), + element: signal(el as HTMLElement), + disabled: signal(false), + rowspan: signal(opts.rowspan ?? 1), + colspan: signal(opts.rowspan ?? 1), + rowindex: signal(-1), + colindex: signal(-1), + }; + coordinates = computed(() => gridFocus()?.getCoordinates(cell) ?? {row: -1, col: -1}); + cell.rowindex = computed(() => coordinates().row); + cell.colindex = computed(() => coordinates().col); + return cell; +} + +export function createTestCells( + gridFocus: SignalLike | undefined>, + numRows: number, + numCols: number, +): WritableSignalLike { + return signal( + Array.from({length: numRows}).map((_, r) => + Array.from({length: numCols}).map((_, c) => { + return createTestCell(gridFocus, {id: `cell-${r}-${c}`}); + }), + ), + ); +} + +// Main helper function to instantiate GridFocus and its dependencies for testing +export function setupGridFocus(inputs: TestSetupInputs = {}): { + cells: TestGridCell[][]; + gridFocus: GridFocus; +} { + const numRows = inputs.numRows ?? 3; + const numCols = inputs.numCols ?? 3; + + const gridFocus = inputs.gridFocus ?? signal | undefined>(undefined); + const cells = inputs.cells ?? createTestCells(gridFocus, numRows, numCols); + + const activeCoords = inputs.activeCoords ?? signal({row: 0, col: 0}); + const focusMode = signal<'roving' | 'activedescendant'>( + inputs.focusMode ? inputs.focusMode() : 'roving', + ); + const disabled = signal(inputs.disabled ? inputs.disabled() : false); + const softDisabled = signal(inputs.softDisabled ? inputs.softDisabled() : false); + + gridFocus.set( + new GridFocus({ + cells: cells, + activeCoords: activeCoords, + focusMode: focusMode, + disabled: disabled, + softDisabled: softDisabled, + }), + ); + + return { + cells: cells(), + gridFocus: gridFocus()!, + }; +} + +describe('GridFocus', () => { + describe('Initialization', () => { + it('should initialize with activeCell at {row: 0, col: 0} by default', () => { + const {gridFocus} = setupGridFocus(); + expect(gridFocus.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('should compute activeCell based on activeCell', () => { + const {gridFocus, cells} = setupGridFocus({ + activeCoords: signal({row: 1, col: 1}), + }); + expect(gridFocus.activeCell()).toBe(cells[1][1]); + }); + + it('should compute activeCell correctly when rowspan and colspan are set', () => { + const activeCoords = signal({row: 0, col: 0}); + const gridFocusSignal = signal | undefined>(undefined); + + // Visualization of this irregular grid. + // + // +---+---+---+ + // | |0,2| + // + 0,0 +---+ + // | |1,2| + // +---+---+---+ + // + const cell_0_0 = createTestCell(gridFocusSignal, {id: `cell-0-0`, rowspan: 2, colspan: 2}); + const cell_0_2 = createTestCell(gridFocusSignal, {id: `cell-0-2`}); + const cell_1_2 = createTestCell(gridFocusSignal, {id: `cell-1-2`}); + const cells = signal([[cell_0_0, cell_0_2], [cell_1_2]]); + + const {gridFocus} = setupGridFocus({ + cells, + activeCoords, + gridFocus: gridFocusSignal, + }); + + activeCoords.set({row: 0, col: 0}); + expect(gridFocus.activeCell()).toBe(cell_0_0); + activeCoords.set({row: 0, col: 1}); + expect(gridFocus.activeCell()).toBe(cell_0_0); + activeCoords.set({row: 1, col: 0}); + expect(gridFocus.activeCell()).toBe(cell_0_0); + activeCoords.set({row: 1, col: 1}); + expect(gridFocus.activeCell()).toBe(cell_0_0); + + activeCoords.set({row: 0, col: 2}); + expect(gridFocus.activeCell()).toBe(cell_0_2); + + activeCoords.set({row: 1, col: 2}); + expect(gridFocus.activeCell()).toBe(cell_1_2); + }); + + it('should compute rowCount and colCount correctly', () => { + const {gridFocus} = setupGridFocus({ + numRows: 2, + numCols: 3, + }); + expect(gridFocus.rowCount()).toBe(2); + expect(gridFocus.colCount()).toBe(3); + }); + + it('should compute rowCount and colCount correctly when rowspan and colspan are set', () => { + const gridFocusSignal = signal | undefined>(undefined); + + // Visualization of this irregular grid. + // + // +---+---+---+ + // | |0,2| + // + 0,0 +---+ + // | |1,2| + // +---+---+---+ + // + const cell_0_0 = createTestCell(gridFocusSignal, {id: `cell-0-0`, rowspan: 2, colspan: 2}); + const cell_0_2 = createTestCell(gridFocusSignal, {id: `cell-0-2`}); + const cell_1_2 = createTestCell(gridFocusSignal, {id: `cell-1-2`}); + const cells = signal([[cell_0_0, cell_0_2], [cell_1_2]]); + + const {gridFocus} = setupGridFocus({ + cells, + gridFocus: gridFocusSignal, + }); + + expect(gridFocus.rowCount()).toBe(2); + expect(gridFocus.colCount()).toBe(3); + }); + }); + + describe('isGridDisabled', () => { + it('should return true if inputs.disabled is true', () => { + const {gridFocus} = setupGridFocus({disabled: signal(true)}); + expect(gridFocus.isGridDisabled()).toBeTrue(); + }); + + it('should return true if all cells are disabled', () => { + const {gridFocus, cells} = setupGridFocus({numRows: 2, numCols: 1}); + cells.forEach(row => row.forEach(cell => cell.disabled.set(true))); + expect(gridFocus.isGridDisabled()).toBeTrue(); + }); + + it('should return true if inputs.cells is empty', () => { + const {gridFocus} = setupGridFocus({numRows: 0, numCols: 0}); + expect(gridFocus.isGridDisabled()).toBeTrue(); + }); + + it('should return true if the grid contains only empty rows', () => { + const cells = signal([[], []]); + const {gridFocus} = setupGridFocus({cells: cells}); + expect(gridFocus.isGridDisabled()).toBeTrue(); + }); + }); + + describe('getActiveDescendant', () => { + it('should return undefined if focusMode is "roving"', () => { + const {gridFocus} = setupGridFocus({focusMode: signal('roving')}); + expect(gridFocus.getActiveDescendant()).toBeUndefined(); + }); + + it('should return undefined if the grid is disabled', () => { + const {gridFocus} = setupGridFocus({ + disabled: signal(true), + focusMode: signal('activedescendant'), + }); + expect(gridFocus.getActiveDescendant()).toBeUndefined(); + }); + + it('should return the activeCell id if focusMode is "activedescendant"', () => { + const {gridFocus, cells} = setupGridFocus({ + focusMode: signal('activedescendant'), + activeCoords: signal({row: 2, col: 2}), + }); + expect(gridFocus.getActiveDescendant()).toBe(cells[2][2].id()); + }); + }); + + describe('getGridTabindex', () => { + it('should return 0 if grid is disabled', () => { + const {gridFocus} = setupGridFocus({disabled: signal(true)}); + expect(gridFocus.getGridTabIndex()).toBe(0); + }); + + it('should return -1 if focusMode is "roving" and grid is not disabled', () => { + const {gridFocus} = setupGridFocus({focusMode: signal('roving')}); + expect(gridFocus.getGridTabIndex()).toBe(-1); + }); + + it('should return 0 if focusMode is "activedescendant" and grid is not disabled', () => { + const {gridFocus} = setupGridFocus({focusMode: signal('activedescendant')}); + expect(gridFocus.getGridTabIndex()).toBe(0); + }); + }); + + describe('getCellTabindex', () => { + it('should return -1 if grid is disabled', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + disabled: signal(true), + }); + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); + }); + + it('should return -1 if focusMode is "activedescendant"', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + focusMode: signal('activedescendant'), + }); + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); + }); + + it('should return 0 if focusMode is "roving" and cell is the activeCell', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + focusMode: signal('roving'), + }); + + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(0); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); + }); + }); + + describe('isFocusable', () => { + it('should return true if cell is not disabled', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + }); + expect(gridFocus.isFocusable(cells[0][0])).toBeTrue(); + expect(gridFocus.isFocusable(cells[0][1])).toBeTrue(); + expect(gridFocus.isFocusable(cells[0][2])).toBeTrue(); + }); + + it('should return false if cell is disabled and softDisabled is false', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + softDisabled: signal(false), + }); + cells[0][1].disabled.set(true); + expect(gridFocus.isFocusable(cells[0][0])).toBeTrue(); + expect(gridFocus.isFocusable(cells[0][1])).toBeFalse(); + expect(gridFocus.isFocusable(cells[0][2])).toBeTrue(); + }); + + it('should return true if cell is disabled but softDisabled is true', () => { + const {gridFocus, cells} = setupGridFocus({ + numRows: 1, + numCols: 3, + softDisabled: signal(true), + }); + cells[0][1].disabled.set(true); + expect(gridFocus.isFocusable(cells[0][0])).toBeTrue(); + expect(gridFocus.isFocusable(cells[0][1])).toBeTrue(); + expect(gridFocus.isFocusable(cells[0][2])).toBeTrue(); + }); + }); + + describe('focusCoordinates', () => { + it('should return false and not change state if grid is disabled', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + disabled: signal(true), + }); + + const success = gridFocus.focusCoordinates({row: 1, col: 0}); + + expect(success).toBeFalse(); + expect(activeCoords()).toEqual({row: 0, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + }); + + it('should return false and not change state if cell is not focusable', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({activeCoords}); + cells[1][0].disabled.set(true); + + const success = gridFocus.focusCoordinates({row: 1, col: 0}); + + expect(success).toBeFalse(); + expect(activeCoords()).toEqual({row: 0, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + }); + + it('should focus cell, update activeCell and prevActiveCell in "roving" mode', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + focusMode: signal('roving'), + }); + + const success = gridFocus.focusCoordinates({row: 1, col: 0}); + + expect(success).toBeTrue(); + expect(activeCoords()).toEqual({row: 1, col: 0}); + expect(cells[1][0].element().focus).toHaveBeenCalled(); + + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.prevActiveCoords()).toEqual({row: 0, col: 0}); + }); + + it('should update activeCell and prevActiveCell but not call element.focus in "activedescendant" mode', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + focusMode: signal('activedescendant'), + }); + + const success = gridFocus.focusCoordinates({row: 1, col: 0}); + + expect(success).toBeTrue(); + expect(activeCoords()).toEqual({row: 1, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.prevActiveCoords()).toEqual({row: 0, col: 0}); + }); + }); + + describe('focusCell', () => { + it('should return false and not change state if grid is disabled', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + disabled: signal(true), + }); + + const success = gridFocus.focusCell(cells[1][0]); + + expect(success).toBeFalse(); + expect(activeCoords()).toEqual({row: 0, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + }); + + it('should return false and not change state if cell is not focusable', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({activeCoords}); + cells[1][0].disabled.set(true); + + const success = gridFocus.focusCell(cells[1][0]); + + expect(success).toBeFalse(); + expect(activeCoords()).toEqual({row: 0, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + }); + + it('should focus cell, update activeCell and prevActiveCell in "roving" mode', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + focusMode: signal('roving'), + }); + + const success = gridFocus.focusCell(cells[1][0]); + + expect(success).toBeTrue(); + expect(activeCoords()).toEqual({row: 1, col: 0}); + expect(cells[1][0].element().focus).toHaveBeenCalled(); + + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.prevActiveCoords()).toEqual({row: 0, col: 0}); + }); + + it('should update activeCell and prevActiveCell but not call element.focus in "activedescendant" mode', () => { + const activeCoords = signal({row: 0, col: 0}); + const {gridFocus, cells} = setupGridFocus({ + activeCoords, + focusMode: signal('activedescendant'), + }); + + const success = gridFocus.focusCell(cells[1][0]); + + expect(success).toBeTrue(); + expect(activeCoords()).toEqual({row: 1, col: 0}); + expect(cells[1][0].element().focus).not.toHaveBeenCalled(); + + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.prevActiveCoords()).toEqual({row: 0, col: 0}); + }); + }); +}); diff --git a/src/aria/private/behaviors/grid-focus/grid-focus.ts b/src/aria/private/behaviors/grid-focus/grid-focus.ts new file mode 100644 index 000000000000..508026eb9726 --- /dev/null +++ b/src/aria/private/behaviors/grid-focus/grid-focus.ts @@ -0,0 +1,223 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; + +/** Represents an cell in a grid, such as a grid cell, that may receive focus. */ +export interface GridFocusCell { + /** A unique identifier for the cell. */ + id: SignalLike; + + /** The html element that should receive focus. */ + element: SignalLike; + + /** Whether an cell is disabled. */ + disabled: SignalLike; + + /** The number of rows the cell should span. Defaults to 1. */ + rowspan: SignalLike; + + /** The number of columns the cell should span. Defaults to 1. */ + colspan: SignalLike; + + /** The row index of the cell within the grid. */ + rowindex: SignalLike; + + /** The column index of the cell within the grid. */ + colindex: SignalLike; +} + +/** Represents the required inputs for a grid that contains focusable cells. */ +export interface GridFocusInputs { + /** The focus strategy used by the grid. */ + focusMode: SignalLike<'roving' | 'activedescendant'>; + + /** Whether the grid is disabled. */ + disabled: SignalLike; + + /** The cells in the grid, represented as a 2D array (rows and columns). */ + cells: SignalLike; + + /** The coordinates (row and column) of the current active cell. */ + activeCoords: WritableSignalLike; + + /** Whether disabled cells in the grid should be focusable. */ + softDisabled: SignalLike; +} + +/** Represents coordinates in a grid. */ +export interface RowCol { + /** The row index. */ + row: number; + + /** The column index. */ + col: number; +} + +/** Controls focus for a 2D grid of cells. */ +export class GridFocus { + /** The last active cell coordinates. */ + prevActiveCoords = signal({row: 0, col: 0}); + + /** The current active cell based on `activeCoords` coordinates. */ + activeCell = computed(() => this.getCell(this.inputs.activeCoords())); + + /** The number of rows in the grid. */ + rowCount = computed(() => this.inputs.cells().length); + + /** The number of columns in the grid. */ + colCount = computed(() => { + return this.inputs.cells()[0].reduce((count, curr) => count + curr.colspan(), 0); + }); + + constructor(readonly inputs: GridFocusInputs) {} + + /** The id of the current active cell, for ARIA activedescendant. */ + getActiveDescendant(): string | undefined { + if (this.isGridDisabled() || this.inputs.focusMode() === 'roving') { + return undefined; + } + const currentActiveCell = this.activeCell(); + return currentActiveCell ? currentActiveCell.id() : undefined; + } + + /** Whether the grid is in a disabled state. */ + isGridDisabled(): boolean { + if (this.inputs.disabled()) { + return true; + } + const gridCells = this.inputs.cells(); + return gridCells.length === 0 || gridCells.every(row => row.every(cell => cell.disabled())); + } + + /** The tab index for the grid container. */ + getGridTabIndex(): -1 | 0 { + if (this.isGridDisabled()) { + return 0; + } + return this.inputs.focusMode() === 'activedescendant' ? 0 : -1; + } + + /** Returns the tab index for the given grid cell cell. */ + getCellTabIndex(cell: T): -1 | 0 { + if (this.isGridDisabled()) { + return -1; + } + if (this.inputs.focusMode() === 'activedescendant') { + return -1; + } + return this.activeCell() === cell ? 0 : -1; + } + + /** Focuses the given cell. */ + focusCell(cell: T): boolean { + if (this.isGridDisabled()) { + return false; + } + + if (!this.isFocusable(cell)) { + return false; + } + + this.prevActiveCoords.set(this.inputs.activeCoords()); + this.inputs.activeCoords.set({row: cell.rowindex(), col: cell.colindex()}); + this._focus(cell); + + return true; + } + + /** Moves focus to the cell at the given coordinates if it's part of a focusable cell. */ + focusCoordinates(coordinates: RowCol): boolean { + if (this.isGridDisabled()) { + return false; + } + + const cell = this.getCell(coordinates); + + if (!cell || !this.isFocusable(cell)) { + return false; + } + + this.prevActiveCoords.set(this.inputs.activeCoords()); + this.inputs.activeCoords.set(coordinates); + this._focus(cell); + + return true; + } + + /** Handles conditionally calling `focus` on the HTML element of the cell. */ + private _focus(cell: T) { + if (this.inputs.focusMode() === 'roving') { + const element = cell.element(); + if (element && typeof element.focus === 'function') { + element.focus(); + } + } + } + + /** Returns true if the given cell can be navigated to. */ + isFocusable(cell: T): boolean { + return !cell.disabled() || this.inputs.softDisabled(); + } + + /** Finds the top-left anchor coordinates of a given cell instance in the grid. */ + getCoordinates(cellToFind: T): RowCol | void { + const grid = this.inputs.cells(); + const occupiedCells = new Set(); + + for (let rowindex = 0; rowindex < grid.length; rowindex++) { + let colindex = 0; + const gridRow = grid[rowindex]; + + for (const gridCell of gridRow) { + // Skip past cells that are already taken. + while (occupiedCells.has(`${rowindex},${colindex}`)) { + colindex++; + } + + // Check if this is the cell we're looking for. + if (gridCell === cellToFind) { + return {row: rowindex, col: colindex}; + } + + const rowspan = gridCell.rowspan(); + const colspan = gridCell.colspan(); + + // If this cell spans multiple rows, mark those cells as occupied. + if (rowspan > 1) { + for (let rOffset = 1; rOffset < rowspan; rOffset++) { + const spannedRow = rowindex + rOffset; + for (let cOffset = 0; cOffset < colspan; cOffset++) { + const spannedCol = colindex + cOffset; + occupiedCells.add(`${spannedRow},${spannedCol}`); + } + } + } + + colindex += colspan; + } + } + } + + /** Gets the cell that covers the given coordinates, considering rowspan and colspan. */ + getCell(coords: RowCol): T | void { + for (const row of this.inputs.cells()) { + for (const cell of row) { + if ( + coords.row >= cell.rowindex() && + coords.row <= cell.rowindex() + cell.rowspan() - 1 && + coords.col >= cell.colindex() && + coords.col <= cell.colindex() + cell.colspan() - 1 + ) { + return cell; + } + } + } + } +} diff --git a/src/aria/private/behaviors/grid-navigation/BUILD.bazel b/src/aria/private/behaviors/grid-navigation/BUILD.bazel new file mode 100644 index 000000000000..eb67f76f4e6d --- /dev/null +++ b/src/aria/private/behaviors/grid-navigation/BUILD.bazel @@ -0,0 +1,30 @@ +load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") + +package(default_visibility = ["//visibility:public"]) + +ts_project( + name = "grid-navigation", + srcs = ["grid-navigation.ts"], + deps = [ + "//:node_modules/@angular/core", + "//src/aria/private/behaviors/grid-focus", + "//src/aria/private/behaviors/signal-like", + ], +) + +ng_project( + name = "unit_test_sources", + testonly = True, + srcs = ["grid-navigation.spec.ts"], + deps = [ + ":grid-navigation", + "//:node_modules/@angular/core", + "//src/aria/private/behaviors/grid-focus", + "//src/aria/private/behaviors/signal-like", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_test_sources"], +) diff --git a/src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts b/src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts new file mode 100644 index 000000000000..ddbd297fca6c --- /dev/null +++ b/src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts @@ -0,0 +1,1253 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal, WritableSignalLike} from '../../behaviors/signal-like/signal-like'; +import {GridFocus} from '../grid-focus/grid-focus'; +import {GridNavigation, GridNavigationCell, GridNavigationInputs} from './grid-navigation'; + +type TestGridNav = GridNavigation; + +interface TestCell extends GridNavigationCell { + disabled: WritableSignalLike; +} + +interface TestCellInputs { + rowspan?: number; + colspan?: number; +} + +function createCell(config?: TestCellInputs): TestCell { + const element = document.createElement('div'); + spyOn(element, 'focus').and.callThrough(); + + return { + id: signal(''), + element: signal(element), + disabled: signal(false), + rowindex: signal(0), + colindex: signal(0), + rowspan: signal(config?.rowspan ?? 1), + colspan: signal(config?.colspan ?? 1), + }; +} + +type TestGridNavInputs = Partial> & + Pick, 'cells'>; + +function createGridNav(config: TestGridNavInputs): {gridNav: TestGridNav; cells: TestCell[][]} { + const wrap = signal(true); + const disabled = signal(false); + const softDisabled = signal(true); + const focusMode = signal('roving' as const); + const activeCoords = signal({row: 0, col: 0}); + const wrapBehavior = signal('continuous' as const); + + const gridFocus = new GridFocus({ + disabled, + focusMode, + activeCoords, + softDisabled, + ...config, + }); + + const gridNav = new GridNavigation({ + wrap, + disabled, + focusMode, + activeCoords, + softDisabled, + wrapBehavior, + gridFocus, + ...config, + }); + + for (const row of config.cells()) { + for (const cell of row) { + const coordinates = computed(() => gridFocus.getCoordinates(cell) ?? {row: -1, col: -1}); + cell.rowindex = computed(() => coordinates().row); + cell.colindex = computed(() => coordinates().col); + } + } + + return {gridNav, cells: config.cells()}; +} + +describe('GridNavigation', () => { + /** + * GRID A: + * ┌─────┬─────┬─────┐ + * │ 0,0 │ 0,1 │ 0,2 │ + * ├─────┼─────┼─────┤ + * │ 1,0 │ 1,1 │ 1,2 │ + * ├─────┼─────┼─────┤ + * │ 2,0 │ 2,1 │ 2,2 │ + * └─────┴─────┴─────┘ + */ + let gridA = signal([]); + + /** + * GRID B: + * ┌─────┬─────┬─────┐ + * │ 0,0 │ 0,1 │ 0,2 │ + * ├─────┼─────┤ │ + * │ 1,0 │ 1,1 │ │ + * ├─────┤ ├─────┤ + * │ 2,0 │ │ 2,2 │ + * │ ├─────┼─────┤ + * │ │ 3,1 │ 3,2 │ + * └─────┴─────┴─────┘ + */ + let gridB = signal([]); + + /** + * GRID C: + * ┌───────────┬─────┬─────┐ + * │ 0,0 │ 0,2 │ 0,3 │ + * ├─────┬─────┴─────┼─────┤ + * │ 1,0 │ 1,1 │ 1,3 │ + * ├─────┼─────┬─────┴─────┤ + * │ 2,0 │ 2,1 │ 2,2 │ + * └─────┴─────┴───────────┘ + */ + let gridC = signal([]); + + /** + * GRID D: + * ┌─────┬───────────┬─────┐ + * │ 0,0 │ 0,1 │ 0,3 │ + * │ ├───────────┼─────┤ + * │ │ 1,1 │ 1,3 │ + * ├─────┤ │ │ + * │ 2,0 │ │ │ + * ├─────┼─────┬─────┴─────┤ + * │ 3,0 │ 3,1 │ 3,2 │ + * └─────┴─────┴───────────┘ + */ + let gridD = signal([]); + + beforeEach(() => { + gridA.set([ + [createCell(), createCell(), createCell()], + [createCell(), createCell(), createCell()], + [createCell(), createCell(), createCell()], + ]); + + gridB.set([ + [createCell(), createCell(), createCell({rowspan: 2})], + [createCell(), createCell({rowspan: 2})], + [createCell({rowspan: 2}), createCell()], + [createCell(), createCell()], + ]); + + gridC.set([ + [createCell({colspan: 2}), createCell(), createCell()], + [createCell(), createCell({colspan: 2}), createCell()], + [createCell(), createCell(), createCell({colspan: 2})], + ]); + + gridD.set([ + [createCell({rowspan: 2}), createCell({colspan: 2}), createCell()], + [createCell({rowspan: 2, colspan: 2}), createCell({rowspan: 2})], + [createCell()], + [createCell(), createCell(), createCell({colspan: 2})], + ]); + }); + + describe('up()', () => { + it('should navigate up', () => { + const {gridNav} = createGridNav({ + cells: gridA, + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.up(); + expect(result).toBeTrue(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('(wrap: false) should not wrap', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(false), + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('(soft disabled: true) should be able to navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][1].disabled.set(true); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(false), + activeCoords: signal({row: 2, col: 1}), + }); + cells[1][1].disabled.set(true); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(false), + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][1].disabled.set(true); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('(disabled: true) should not navigate', () => { + const {gridNav} = createGridNav({ + cells: gridA, + disabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.up(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + describe('(wrap: true)', () => { + describe('(wrap behavior: loop)', () => { + it('should loop to the last cell of the current column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 0, col: 1}), + }); + cells[2][1].disabled.set(true); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 0, col: 1}), + }); + cells[1][1].disabled.set(true); + cells[2][1].disabled.set(true); + const result = gridNav.up(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + }); + + describe('(wrap behavior: continuous)', () => { + it('should wrap to the last cell of the previous column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + }); + + it('should wrap to the last cell of the last column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 0, col: 0}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 0, col: 1}), + }); + + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + + cells[1][1].disabled.set(true); + cells[2][1].disabled.set(true); + + cells[0][2].disabled.set(true); + cells[2][2].disabled.set(true); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 0, col: 1}), + }); + + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + + cells[1][1].disabled.set(true); + cells[2][1].disabled.set(true); + + cells[0][2].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][2].disabled.set(true); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][1].disabled.set(true); + cells[0][2].disabled.set(true); + cells[1][2].disabled.set(true); + cells[2][2].disabled.set(true); + + const result = gridNav.up(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + }); + }); + + describe('with rowspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridB, + activeCoords: signal({row: 3, col: 2}), + }); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 2}); + }); + + it('should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridB, + activeCoords: signal({row: 3, col: 0}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridB, + softDisabled: signal(false), + activeCoords: signal({row: 2, col: 2}), + }); + cells[0][2].disabled.set(true); + cells[2][0].disabled.set(true); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(wrap: false) should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridB, + wrap: signal(false), + activeCoords: signal({row: 1, col: 2}), + }); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + }); + }); + + describe('with colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridC, + activeCoords: signal({row: 2, col: 3}), + }); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 3}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 3}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridC, + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 2}), + }); + cells[0][0].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][1].disabled.set(true); + + const result = gridNav.up(); + expect(result).toBeTrue(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 3}); + }); + }); + + describe('with rowspan and colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridD, + activeCoords: signal({row: 3, col: 3}), + }); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 3}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 3}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridD, + softDisabled: signal(false), + activeCoords: signal({row: 3, col: 3}), + }); + + cells[1][0].disabled.set(true); + cells[1][1].disabled.set(true); + + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 1}); + gridNav.up(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + }); + }); + + describe('down()', () => { + it('should navigate down', () => { + const {gridNav} = createGridNav({ + cells: gridA, + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.down(); + expect(result).toBeTrue(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + + it('(wrap: false) should not wrap', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(false), + activeCoords: signal({row: 2, col: 1}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + + it('(soft disabled: true) should be able to navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + cells[2][1].disabled.set(true); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(false), + activeCoords: signal({row: 0, col: 1}), + }); + cells[1][1].disabled.set(true); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(false), + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 1}), + }); + cells[2][1].disabled.set(true); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('(disabled: true) should not navigate', () => { + const {gridNav} = createGridNav({ + cells: gridA, + disabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.down(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + describe('(wrap: true)', () => { + describe('(wrap behavior: loop)', () => { + it('should loop to the first cell of the current column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 2, col: 1}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 2, col: 1}), + }); + cells[0][1].disabled.set(true); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 2, col: 1}), + }); + cells[0][1].disabled.set(true); + cells[1][1].disabled.set(true); + const result = gridNav.down(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); + }); + }); + + describe('(wrap behavior: continuous)', () => { + it('should wrap to the first cell of the next column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 2, col: 1}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 2, col: 1}), + }); + + cells[0][2].disabled.set(true); + cells[1][2].disabled.set(true); + + gridNav.down(); + // Should land on (2,2) + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][1].disabled.set(true); + cells[0][2].disabled.set(true); + cells[1][2].disabled.set(true); + cells[2][2].disabled.set(true); + + const result = gridNav.down(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should wrap to the first cell of the first column', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 2, col: 2}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); + }); + + describe('with rowspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridB, + activeCoords: signal({row: 0, col: 0}), + }); + + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridB, + activeCoords: signal({row: 1, col: 2}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridB, + softDisabled: signal(false), + activeCoords: signal({row: 0, col: 0}), + }); + cells[1][0].disabled.set(true); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('(wrap: false) should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridB, + wrap: signal(false), + activeCoords: signal({row: 0, col: 2}), + }); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + }); + + describe('with colspan set', () => { + // For `down()`, colspan doesn't affect vertical navigation as much as rowspan. + // Basic navigation should still work. + it('should navigate correctly', () => { + const {gridNav} = createGridNav({cells: gridC, activeCoords: signal({row: 0, col: 0})}); + gridNav.down(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + }); + }); + + describe('left()', () => { + it('should navigate left', () => { + const {gridNav} = createGridNav({ + cells: gridA, + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.left(); + expect(result).toBeTrue(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(wrap: false) should not wrap', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(false), + activeCoords: signal({row: 1, col: 0}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(soft disabled: true) should be able to navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + cells[1][0].disabled.set(true); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 2}), + }); + cells[1][1].disabled.set(true); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(false), + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 1}), + }); + cells[1][0].disabled.set(true); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('(disabled: true) should not navigate', () => { + const {gridNav} = createGridNav({ + cells: gridA, + disabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.left(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + describe('(wrap: true)', () => { + describe('(wrap behavior: loop)', () => { + it('should loop to the last cell of the current row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 0}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 0}), + }); + cells[1][2].disabled.set(true); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 0}), + }); + cells[1][2].disabled.set(true); + cells[1][1].disabled.set(true); + const result = gridNav.left(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + }); + + describe('(wrap behavior: continuous)', () => { + it('should wrap to the last cell of the previous row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 0}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 0}), + }); + + cells[0][2].disabled.set(true); + cells[0][1].disabled.set(true); + + gridNav.left(); + // Should land on (0,0) + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][1].disabled.set(true); + cells[0][2].disabled.set(true); + cells[1][2].disabled.set(true); + cells[2][2].disabled.set(true); + const result = gridNav.left(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should wrap to the last cell of the last row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 0, col: 0}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + }); + }); + + describe('with rowspan set', () => { + // For `left()`, rowspan doesn't affect horizontal navigation as much as colspan. + // Basic navigation should still work. + it('should navigate correctly', () => { + const {gridNav} = createGridNav({cells: gridB, activeCoords: signal({row: 0, col: 1})}); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); + + describe('with colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridC, + activeCoords: signal({row: 0, col: 3}), + }); + + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridC, + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridC, + softDisabled: signal(false), + activeCoords: signal({row: 0, col: 3}), + }); + + cells[0][1].disabled.set(true); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('(wrap: false) should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridC, + wrap: signal(false), + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); + + describe('with rowspan and colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridD, + activeCoords: signal({row: 0, col: 3}), + }); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.left(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); + }); + + describe('right()', () => { + it('should navigate right', () => { + const {gridNav} = createGridNav({ + cells: gridA, + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.right(); + expect(result).toBeTrue(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('(wrap: false) should not wrap', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(false), + activeCoords: signal({row: 1, col: 2}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('(soft disabled: true) should be able to navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + cells[1][2].disabled.set(true); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 0}), + }); + cells[1][1].disabled.set(true); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(false), + softDisabled: signal(false), + activeCoords: signal({row: 1, col: 1}), + }); + cells[1][2].disabled.set(true); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('(disabled: true) should not navigate', () => { + const {gridNav} = createGridNav({ + cells: gridA, + disabled: signal(true), + activeCoords: signal({row: 1, col: 1}), + }); + const result = gridNav.right(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + describe('(wrap: true)', () => { + describe('(wrap behavior: loop)', () => { + it('should loop to the first cell of the current row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 2}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 2}), + }); + cells[1][0].disabled.set(true); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('loop'), + activeCoords: signal({row: 1, col: 2}), + }); + cells[1][0].disabled.set(true); + cells[1][1].disabled.set(true); + const result = gridNav.right(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); + }); + }); + + describe('(wrap behavior: continuous)', () => { + it('should wrap to the first cell of the next row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 2}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 0}); + }); + + it('should wrap until it finds a cell that is focusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 2}), + }); + + cells[2][0].disabled.set(true); + cells[2][1].disabled.set(true); + + gridNav.right(); + // Should land on (2,2) + expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('should not navigate if all cells that would be navigated to are unfocusable', () => { + const {gridNav, cells} = createGridNav({ + cells: gridA, + wrap: signal(true), + softDisabled: signal(false), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 1, col: 1}), + }); + cells[0][0].disabled.set(true); + cells[1][0].disabled.set(true); + cells[2][0].disabled.set(true); + cells[0][1].disabled.set(true); + cells[2][1].disabled.set(true); + cells[0][2].disabled.set(true); + cells[1][2].disabled.set(true); + cells[2][2].disabled.set(true); + const result = gridNav.right(); + expect(result).toBeFalse(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should wrap to the first cell of the first row', () => { + const {gridNav} = createGridNav({ + cells: gridA, + wrap: signal(true), + wrapBehavior: signal('continuous'), + activeCoords: signal({row: 2, col: 2}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); + }); + + describe('with rowspan set', () => { + // For `right()`, rowspan doesn't affect horizontal navigation as much as colspan. + // Basic navigation should still work. + it('should navigate correctly', () => { + const {gridNav} = createGridNav({cells: gridB, activeCoords: signal({row: 0, col: 0})}); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + }); + }); + + describe('with colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridC, + activeCoords: signal({row: 0, col: 0}), + }); + + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridC, + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + }); + + it('(soft disabled: false) should skip disabled cells', () => { + const {gridNav, cells} = createGridNav({ + cells: gridC, + softDisabled: signal(false), + activeCoords: signal({row: 0, col: 0}), + }); + cells[0][1].disabled.set(true); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + }); + + it('(wrap: false) should navigate correctly when in a subcoordinate of a cell', () => { + const {gridNav} = createGridNav({ + cells: gridC, + wrap: signal(false), + activeCoords: signal({row: 0, col: 1}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); + }); + }); + + describe('with rowspan and colspan set', () => { + it('should navigate correctly', () => { + const {gridNav} = createGridNav({ + cells: gridD, + activeCoords: signal({row: 0, col: 0}), + }); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); + gridNav.right(); + expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 3}); + }); + }); + }); +}); diff --git a/src/aria/private/behaviors/grid-navigation/grid-navigation.ts b/src/aria/private/behaviors/grid-navigation/grid-navigation.ts new file mode 100644 index 000000000000..62cce7975bcf --- /dev/null +++ b/src/aria/private/behaviors/grid-navigation/grid-navigation.ts @@ -0,0 +1,158 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {SignalLike} from '../signal-like/signal-like'; +import {GridFocus, GridFocusCell, GridFocusInputs, RowCol} from '../grid-focus/grid-focus'; +import {computed} from '@angular/core'; + +/** Represents an item in a collection, such as a listbox option, than can be navigated to. */ +export interface GridNavigationCell extends GridFocusCell {} + +/** Represents the required inputs for a collection that has navigable items. */ +export interface GridNavigationInputs extends GridFocusInputs { + gridFocus: GridFocus; + wrap: SignalLike; + wrapBehavior: SignalLike<'continuous' | 'loop'>; +} + +/** Controls navigation for a grid of items. */ +export class GridNavigation { + rowcount = computed(() => this.inputs.gridFocus.rowCount()); + colcount = computed(() => this.inputs.gridFocus.colCount()); + + constructor(readonly inputs: GridNavigationInputs) {} + + /** Navigates to the given item. */ + gotoCell(cell?: T): boolean { + return cell ? this.inputs.gridFocus.focusCell(cell) : false; + } + + /** Navigates to the given coordinates. */ + gotoCoords(coords: RowCol): boolean { + return this.inputs.gridFocus.focusCoordinates(coords); + } + + /** Navigates to the item above the current item. */ + up(): boolean { + return this._advance((cell: T, {col}: RowCol) => { + const rowindex = cell.rowindex(); + const isRowWrapping = this.inputs.wrap() && rowindex - 1 < 0; + const isColumnWrapping = isRowWrapping && this.inputs.wrapBehavior() === 'continuous'; + + const nextCoords = { + row: isRowWrapping + ? (rowindex - 1 + this.rowcount()) % this.rowcount() + : Math.max(rowindex - 1, 0), + col: isColumnWrapping ? (col - 1 + this.colcount()) % this.colcount() : col, + }; + + const nextCell = this.inputs.gridFocus.getCell(nextCoords)!; + + return { + row: nextCell.rowindex(), + col: nextCoords.col, + }; + }); + } + + /** Navigates to the item below the current item. */ + down(): boolean { + return this._advance((cell: T, {col}: RowCol) => { + const rowspan = cell.rowspan(); + const rowindex = cell.rowindex(); + const isRowWrapping = this.inputs.wrap() && rowindex + rowspan >= this.rowcount(); + const isColumnWrapping = isRowWrapping && this.inputs.wrapBehavior() === 'continuous'; + + return { + row: isRowWrapping + ? (rowindex + rowspan) % this.rowcount() + : Math.min(rowindex + rowspan, this.rowcount() - 1), + col: isColumnWrapping ? (col + 1 + this.colcount()) % this.colcount() : col, + }; + }); + } + + /** Navigates to the item to the left of the current item. */ + left(): boolean { + return this._advance((cell: T, {row, col}: RowCol) => { + const colindex = cell.colindex(); + const isColumnWrapping = this.inputs.wrap() && colindex - 1 < 0; + const isRowWrapping = isColumnWrapping && this.inputs.wrapBehavior() === 'continuous'; + + const nextCoords = { + row: isRowWrapping ? (row - 1 + this.rowcount()) % this.rowcount() : row, + col: isColumnWrapping + ? (colindex - 1 + this.colcount()) % this.colcount() + : Math.max(colindex - 1, 0), + }; + + const nextCell = this.inputs.gridFocus.getCell(nextCoords)!; + + return { + row: nextCoords.row, + col: nextCell.colindex(), + }; + }); + } + + /** Navigates to the item to the right of the current item. */ + right(): boolean { + return this._advance((cell: T, {row}: RowCol) => { + const colspan = cell.colspan(); + const colindex = cell.colindex(); + const isColumnWrapping = this.inputs.wrap() && colindex + colspan >= this.colcount(); + const isRowWrapping = isColumnWrapping && this.inputs.wrapBehavior() === 'continuous'; + + return { + row: isRowWrapping ? (row + 1 + this.rowcount()) % this.rowcount() : row, + col: isColumnWrapping + ? (colindex + colspan + this.colcount()) % this.colcount() + : Math.min(colindex + colspan, this.colcount() - 1), + }; + }); + } + + /** + * Continuously calls the given stepFn starting at the given coordinates + * until either a new focusable cell is reached or the grid fully loops. + */ + private _advance(stepFn: (cell: T, coords: RowCol) => RowCol) { + const startCoords = this.inputs.activeCoords(); + let prevCoords = {row: startCoords.row, col: startCoords.col}; + let nextCoords = {row: startCoords.row, col: startCoords.col}; + let nextCell = this.inputs.gridFocus.activeCell()!; + + while (true) { + prevCoords = {row: nextCoords.row, col: nextCoords.col}; + nextCoords = stepFn(nextCell, nextCoords); + + // The step did not result in any change in coordinates. + // + // This will happen if the user is at a boundary (start/end row or col) + // and tries to advance past it while `wrap` is false. + if (nextCoords.row === prevCoords.row && nextCoords.col === prevCoords.col) { + return false; + } + + // The step has resulted in arriving back to the original coordinates. + // + // This will happen if the other cells in the grid are unfocusable and `wrap` + // is true. The `stepFn` will eventually loop all the way back to the original cells. + if (nextCoords.row === startCoords.row && nextCoords.col === startCoords.col) { + return false; + } + + nextCell = this.inputs.gridFocus.getCell(nextCoords)!; + + // The `stepFn` has successfully reached a cell that is focusable. + if (this.inputs.gridFocus.isFocusable(nextCell)) { + return this.gotoCoords(nextCoords); + } + } + } +} diff --git a/src/aria/private/behaviors/grid/grid-focus.spec.ts b/src/aria/private/behaviors/grid/grid-focus.spec.ts index f47e40a1a8bb..367891c4a116 100644 --- a/src/aria/private/behaviors/grid/grid-focus.spec.ts +++ b/src/aria/private/behaviors/grid/grid-focus.spec.ts @@ -281,7 +281,7 @@ describe('GridFocus', () => { }); describe('focusCell', () => { - it('should set the active cell and coordinates (#focusCell)', () => { + it('should set the active cell and coordinates', () => { const cells = createTestGrid(createGridA); const gridFocus = setupGridFocus(signal(cells)); @@ -330,7 +330,7 @@ describe('GridFocus', () => { }); describe('focusCoordinates', () => { - it('should set the active cell and coordinates (#focusCoordinates)', () => { + it('should set the active cell and coordinates', () => { const cells = createTestGrid(createGridD); const gridFocus = setupGridFocus(signal(cells)); diff --git a/src/aria/private/behaviors/grid/grid-navigation.spec.ts b/src/aria/private/behaviors/grid/grid-navigation.spec.ts index a120f8e59837..d166d8fcefc1 100644 --- a/src/aria/private/behaviors/grid/grid-navigation.spec.ts +++ b/src/aria/private/behaviors/grid/grid-navigation.spec.ts @@ -141,7 +141,7 @@ describe('GridNavigation', () => { }); describe('up', () => { - it('should get the next coordinates without changing focus (#peek-up)', () => { + it('should get the next coordinates without changing focus', () => { gridNav.gotoCoords({row: 1, col: 0}); const nextCoords = gridNav.peek(direction.Up, gridFocus.activeCoords()); @@ -150,7 +150,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); }); - it('should respect the wrap strategy (#peek-up)', () => { + it('should respect the wrap strategy', () => { const from = {row: 0, col: 0}; gridNav.gotoCoords(from); expect(gridNav.peek(direction.Up, from, 'loop')).toEqual({row: 3, col: 0}); @@ -178,7 +178,7 @@ describe('GridNavigation', () => { expect(nextCoords).toBeUndefined(); }); - it('should get disabled cells when allowDisabled is true and softDisabled is false (up)', () => { + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { softDisabled: signal(false), }); @@ -193,7 +193,7 @@ describe('GridNavigation', () => { }); describe('down', () => { - it('should get the next coordinates without changing focus (#peek-down)', () => { + it('should get the next coordinates without changing focus', () => { gridNav.gotoCoords({row: 1, col: 0}); const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords()); @@ -202,7 +202,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); }); - it('should respect the wrap strategy (#peek-down)', () => { + it('should respect the wrap strategy', () => { const from = {row: 3, col: 1}; gridNav.gotoCoords(from); expect(gridNav.peek(direction.Down, from, 'loop')).toEqual({row: 0, col: 1}); @@ -210,7 +210,7 @@ describe('GridNavigation', () => { expect(gridNav.peek(direction.Down, from, 'continuous')).toEqual({row: 0, col: 2}); }); - it('should return undefined if completely disabled (#peek-down)', () => { + it('should return undefined if completely disabled', () => { cells.flat().forEach(cell => cell.disabled.set(true)); gridNav.gotoCoords({row: 1, col: 0}); @@ -219,7 +219,7 @@ describe('GridNavigation', () => { expect(nextCoords).toBeUndefined(); }); - it('should get disabled cells when allowDisabled is true and softDisabled is false (down)', () => { + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { softDisabled: signal(false), }); @@ -234,7 +234,7 @@ describe('GridNavigation', () => { }); describe('left', () => { - it('should get the next coordinates without changing focus (#peek-left)', () => { + it('should get the next coordinates without changing focus', () => { gridNav.gotoCoords({row: 0, col: 1}); const nextCoords = gridNav.peek(direction.Left, gridFocus.activeCoords()); @@ -243,7 +243,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 0, col: 1}); }); - it('should respect the wrap strategy (#peek-left)', () => { + it('should respect the wrap strategy', () => { const from = {row: 0, col: 0}; gridNav.gotoCoords(from); expect(gridNav.peek(direction.Left, from, 'loop')).toEqual({row: 0, col: 2}); @@ -251,7 +251,7 @@ describe('GridNavigation', () => { expect(gridNav.peek(direction.Left, from, 'continuous')).toBeUndefined(); }); - it('should return undefined if completely disabled (#peek-left)', () => { + it('should return undefined if completely disabled', () => { cells.flat().forEach(cell => cell.disabled.set(true)); gridNav.gotoCoords({row: 1, col: 0}); @@ -260,7 +260,7 @@ describe('GridNavigation', () => { expect(nextCoords).toBeUndefined(); }); - it('should get disabled cells when allowDisabled is true when softDisabled is false (left)', () => { + it('should get disabled cells when allowDisabled is true when softDisabled is false', () => { const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { softDisabled: signal(false), }); @@ -275,7 +275,7 @@ describe('GridNavigation', () => { }); describe('right', () => { - it('should get the next coordinates without changing focus (#peek-right)', () => { + it('should get the next coordinates without changing focus', () => { gridNav.gotoCoords({row: 0, col: 1}); const nextCoords = gridNav.peek(direction.Right, gridFocus.activeCoords()); @@ -284,7 +284,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 0, col: 1}); }); - it('should respect the wrap strategy (#peek-right)', () => { + it('should respect the wrap strategy', () => { const from = {row: 0, col: 2}; gridNav.gotoCoords(from); expect(gridNav.peek(direction.Right, from, 'loop')).toEqual({row: 0, col: 0}); @@ -292,7 +292,7 @@ describe('GridNavigation', () => { expect(gridNav.peek(direction.Right, from, 'continuous')).toEqual({row: 1, col: 0}); }); - it('should return undefined if completely disabled (#peek-right)', () => { + it('should return undefined if completely disabled', () => { cells.flat().forEach(cell => cell.disabled.set(true)); gridNav.gotoCoords({row: 1, col: 0}); @@ -301,7 +301,7 @@ describe('GridNavigation', () => { expect(nextCoords).toBeUndefined(); }); - it('should get disabled cells when allowDisabled is true and softDisabled is false (right)', () => { + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { softDisabled: signal(false), }); @@ -319,8 +319,8 @@ describe('GridNavigation', () => { describe('advance', () => { describe('wrap=continuous', () => { describe('up', () => { - describe('case 1 (3x3 grid)', () => { - it('should advance from (0,0) (up, continuous, case 1)', () => { + describe('case 1', () => { + it('from start', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -335,7 +335,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (2,2) (up, continuous, case 1)', () => { + it('from end', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -365,8 +365,8 @@ describe('GridNavigation', () => { }); }); - describe('case 2 (4x3 grid with spans)', () => { - it('should advance from (0,0) (up, continuous, case 2)', () => { + describe('case 2', () => { + it('from start', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -381,7 +381,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (3,2) (up, continuous, case 2)', () => { + it('from end', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -411,8 +411,8 @@ describe('GridNavigation', () => { }); }); - describe('case 3 (3x4 grid with col spans)', () => { - it('should advance from (0,0) (up, continuous, case 3)', () => { + describe('case 3', () => { + it('from start', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -427,7 +427,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (2,3) (up, continuous, case 3)', () => { + it('from end', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -463,8 +463,8 @@ describe('GridNavigation', () => { }); }); - describe('case 4 (4x4 grid with complex span)', () => { - it('should advance from (0,0) (up, continuous, case 4)', () => { + describe('case 4', () => { + it('from start', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -479,7 +479,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (3,3) (up, continuous, case 4)', () => { + it('from end', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -515,8 +515,8 @@ describe('GridNavigation', () => { }); }); - describe('case 5 (jagged grid)', () => { - it('should advance from (0,0) (up, continuous, case 5)', () => { + describe('case 5', () => { + it('from start', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -531,7 +531,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,2) (up, continuous, case 5)', () => { + it('from end', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -555,8 +555,8 @@ describe('GridNavigation', () => { }); }); - describe('case 6 (grid with empty rows)', () => { - it('should advance from (0,0) (up, continuous, case 6)', () => { + describe('case 6', () => { + it('from start', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -570,7 +570,7 @@ describe('GridNavigation', () => { gridNav.advance(direction.Up); }); - it('should advance from (2,2) (up, continuous, case 6)', () => { + it('from end', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -596,8 +596,8 @@ describe('GridNavigation', () => { }); describe('down', () => { - describe('case 1 (3x3 grid)', () => { - it('should advance from (0,0) (down, continuous, case 1)', () => { + describe('case 1', () => { + it('from start', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -616,7 +616,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); }); - it('should advance from (2,2) (down, continuous, case 1)', () => { + it('from end', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -631,8 +631,8 @@ describe('GridNavigation', () => { }); }); - describe('case 2 (4x3 grid with spans)', () => { - it('should advance from (0,0) (down, continuous, case 2)', () => { + describe('case 2', () => { + it('from start', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -663,7 +663,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); }); - it('should advance from (3,2) (down, continuous, case 2)', () => { + it('from end', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -675,8 +675,8 @@ describe('GridNavigation', () => { }); }); - describe('case 3 (3x4 grid with col spans)', () => { - it('should advance from (0,0) (down, continuous, case 3)', () => { + describe('case 3', () => { + it('from start', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -713,7 +713,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); }); - it('should advance from (2,3) (down, continuous, case 3)', () => { + it('from end', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -725,8 +725,8 @@ describe('GridNavigation', () => { }); }); - describe('case 4 (4x4 grid with complex span)', () => { - it('should advance from (0,0) (down, continuous, case 4)', () => { + describe('case 4', () => { + it('from start', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -763,7 +763,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); }); - it('should advance from (3,3) (down, continuous, case 4)', () => { + it('from end', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -775,8 +775,8 @@ describe('GridNavigation', () => { }); }); - describe('case 5 (jagged grid)', () => { - it('should advance from (0,0) (down, continuous, case 5)', () => { + describe('case 5', () => { + it('from start', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -801,7 +801,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); }); - it('should advance from end (down, continuous, case 5)', () => { + it('from end', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -814,7 +814,7 @@ describe('GridNavigation', () => { }); describe('case 6', () => { - it('should advance from start (down, continuous, case 6)', () => { + it('from start', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -839,7 +839,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); }); - it('should advance from end (down, continuous, case 6)', () => { + it('from end', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -854,7 +854,7 @@ describe('GridNavigation', () => { describe('left', () => { describe('case 1', () => { - it('should advance from start (left, continuous, case 1)', () => { + it('from start', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -870,7 +870,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 1)', () => { + it('from end', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -901,7 +901,7 @@ describe('GridNavigation', () => { }); describe('case 2', () => { - it('should advance from start (left, continuous, case 2)', () => { + it('from start', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -916,7 +916,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 2)', () => { + it('from end', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -953,7 +953,7 @@ describe('GridNavigation', () => { }); describe('case 3', () => { - it('should advance from start (left, continuous, case 3)', () => { + it('from start', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -968,7 +968,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 3)', () => { + it('from end', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -999,7 +999,7 @@ describe('GridNavigation', () => { }); describe('case 4', () => { - it('should advance from start (left, continuous, case 4)', () => { + it('from start', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1014,7 +1014,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 4)', () => { + it('from end', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1051,7 +1051,7 @@ describe('GridNavigation', () => { }); describe('case 5', () => { - it('should advance from start (left, continuous, case 5)', () => { + it('from start', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1066,7 +1066,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 5)', () => { + it('from end', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1093,7 +1093,7 @@ describe('GridNavigation', () => { }); describe('case 6', () => { - it('should advance from start (left, continuous, case 6)', () => { + it('from start', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1108,7 +1108,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from end (left, continuous, case 6)', () => { + it('from end', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1135,7 +1135,7 @@ describe('GridNavigation', () => { describe('right', () => { describe('case 1', () => { - it('should advance from start (right, continuous, case 1)', () => { + it('from start', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1166,7 +1166,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); }); - it('should advance from end (right, continuous, case 1)', () => { + it('from end', () => { const cells = createTestGrid(createGridA); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1182,7 +1182,7 @@ describe('GridNavigation', () => { }); describe('case 2', () => { - it('should advance from start (right, continuous, case 2)', () => { + it('from start', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1219,7 +1219,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); }); - it('should advance from end (right, continuous, case 2)', () => { + it('from end', () => { const cells = createTestGrid(createGridB); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1235,7 +1235,7 @@ describe('GridNavigation', () => { }); describe('case 3', () => { - it('should advance from start (right, continuous, case 3)', () => { + it('from start', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1266,7 +1266,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); }); - it('should advance from end (right, continuous, case 3)', () => { + it('from end', () => { const cells = createTestGrid(createGridC); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1282,7 +1282,7 @@ describe('GridNavigation', () => { }); describe('case 4', () => { - it('should advance from start (right, continuous, case 4)', () => { + it('from start', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1319,7 +1319,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); }); - it('should advance from end (right, continuous, case 4)', () => { + it('from end', () => { const cells = createTestGrid(createGridD); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1335,7 +1335,7 @@ describe('GridNavigation', () => { }); describe('case 5', () => { - it('should advance from start (right, continuous, case 5)', () => { + it('from start', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1362,7 +1362,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); }); - it('should advance from end (right, continuous, case 5)', () => { + it('from end', () => { const cells = createTestGrid(createGridE); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1377,8 +1377,8 @@ describe('GridNavigation', () => { }); }); - describe('case 6 (empty rows)', () => { - it('should advance from (0,0) (right, continuous, case 6)', () => { + describe('case 6', () => { + it('from start', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1403,7 +1403,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); }); - it('should advance from (2,2) (right, continuous, case 6)', () => { + it('from end', () => { const cells = createTestGrid(createGridF); const setup = setupGridNavigation(signal(cells), { rowWrap: signal('continuous'), @@ -1422,7 +1422,7 @@ describe('GridNavigation', () => { describe('wrap=loop', () => { describe('up', () => { - it('should advance (up, loop, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1439,7 +1439,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (up, loop, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1456,7 +1456,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (up, loop, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1473,7 +1473,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (up, loop, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1490,7 +1490,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (up, loop, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1507,7 +1507,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (up, loop, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1524,7 +1524,7 @@ describe('GridNavigation', () => { }); describe('down', () => { - it('should advance (down, loop, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1541,7 +1541,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (down, loop, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1558,7 +1558,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (down, loop, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1575,7 +1575,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (down, loop, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1592,7 +1592,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (down, loop, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1609,7 +1609,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (down, loop, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1626,7 +1626,7 @@ describe('GridNavigation', () => { }); describe('left', () => { - it('should advance (left, loop, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1643,7 +1643,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (left, loop, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1660,7 +1660,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (left, loop, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1677,7 +1677,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (left, loop, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1694,7 +1694,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (left, loop, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1711,7 +1711,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (left, loop, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1730,7 +1730,7 @@ describe('GridNavigation', () => { }); describe('right', () => { - it('should advance (right, loop, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1747,7 +1747,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (right, loop, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1764,7 +1764,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (right, loop, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1781,7 +1781,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (right, loop, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1798,7 +1798,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (right, loop, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1815,7 +1815,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance (right, loop, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('loop'), @@ -1836,7 +1836,7 @@ describe('GridNavigation', () => { describe('wrap=nowrap', () => { describe('up', () => { - it('should advance from (2,0) (up, nowrap, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1853,7 +1853,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (3,1) (up, nowrap, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1870,7 +1870,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); }); - it('should advance from (2,0) (up, nowrap, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1887,7 +1887,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (3,0) (up, nowrap, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1904,7 +1904,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (2,0) (up, nowrap, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1921,7 +1921,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (2,0) (up, nowrap, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1938,7 +1938,7 @@ describe('GridNavigation', () => { }); describe('down', () => { - it('should advance from (0,0) (down, nowrap, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1955,7 +1955,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); }); - it('should advance from (0,1) (down, nowrap, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1972,7 +1972,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); }); - it('should advance from (0,0) (down, nowrap, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -1989,7 +1989,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); }); - it('should advance from (0,0) (down, nowrap, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2006,7 +2006,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); }); - it('should advance from (0,0) (down, nowrap, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2023,7 +2023,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); }); - it('should advance from (0,0) (down, nowrap, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2040,7 +2040,7 @@ describe('GridNavigation', () => { }); describe('left', () => { - it('should advance from (0,2) (left, nowrap, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2057,7 +2057,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,2) (left, nowrap, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2074,7 +2074,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,3) (left, nowrap, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2091,7 +2091,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,3) (left, nowrap, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2108,7 +2108,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,2) (left, nowrap, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2125,7 +2125,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); }); - it('should advance from (0,2) (left, nowrap, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2144,7 +2144,7 @@ describe('GridNavigation', () => { }); describe('right', () => { - it('should advance from (0,0) (right, nowrap, case 1)', () => { + it('case 1', () => { const cells = createTestGrid(createGridA); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2161,7 +2161,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); }); - it('should advance from (0,0) (right, nowrap, case 2)', () => { + it('case 2', () => { const cells = createTestGrid(createGridB); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2178,7 +2178,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); }); - it('should advance from (0,0) (right, nowrap, case 3)', () => { + it('case 3', () => { const cells = createTestGrid(createGridC); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2195,7 +2195,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); }); - it('should advance from (0,0) (right, nowrap, case 4)', () => { + it('case 4', () => { const cells = createTestGrid(createGridD); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2212,7 +2212,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); }); - it('should advance from (0,0) (right, nowrap, case 5)', () => { + it('case 5', () => { const cells = createTestGrid(createGridE); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2229,7 +2229,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); }); - it('should advance from (0,0) (right, nowrap, case 6)', () => { + it('case 6', () => { const cells = createTestGrid(createGridF); const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { rowWrap: signal('nowrap'), @@ -2332,7 +2332,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); }); - it('should get disabled cells when allowDisabled is true and softDisabled is false (first)', () => { + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { const cells = createTestGrid(createGridA); const {gridNav} = setupGridNavigation(signal(cells), {softDisabled: signal(false)}); cells[0][0].disabled.set(true); @@ -2421,7 +2421,7 @@ describe('GridNavigation', () => { expect(gridFocus.activeCoords()).toEqual({row: 1, col: 3}); }); - it('should get disabled cells when allowDisabled is true and softDisabled is false (last)', () => { + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { const cells = createTestGrid(createGridA); const {gridNav} = setupGridNavigation(signal(cells), {softDisabled: signal(false)}); cells[2][2].disabled.set(true); diff --git a/src/aria/private/behaviors/grid/grid.spec.ts b/src/aria/private/behaviors/grid/grid.spec.ts index 4bd80939aea2..8db7756e7728 100644 --- a/src/aria/private/behaviors/grid/grid.spec.ts +++ b/src/aria/private/behaviors/grid/grid.spec.ts @@ -395,7 +395,7 @@ describe('Grid', () => { expect(grid.focusBehavior.activeCoords()).toEqual({row: 1, col: 1}); }); - it('should focus the row above when the last row is deleted', () => { + it('should focus the first cell if active cell and coords are no longer valid', () => { const cellsSignal = signal(createTestGrid(createGridA)); const grid = setupGrid(cellsSignal); grid.gotoCell(cellsSignal()[2][2]); @@ -416,8 +416,8 @@ describe('Grid', () => { expect(grid.focusBehavior.stateStale()).toBe(true); const result = grid.resetState(); expect(result).toBe(true); - expect(grid.focusBehavior.activeCell()).toBe(newCells[1][1]); - expect(grid.focusBehavior.activeCoords()).toEqual({row: 1, col: 1}); + expect(grid.focusBehavior.activeCell()).toBe(newCells[0][0]); + expect(grid.focusBehavior.activeCoords()).toEqual({row: 0, col: 0}); }); }); }); diff --git a/src/aria/private/behaviors/grid/grid.ts b/src/aria/private/behaviors/grid/grid.ts index 37c6ec15def6..6d0316395d38 100644 --- a/src/aria/private/behaviors/grid/grid.ts +++ b/src/aria/private/behaviors/grid/grid.ts @@ -318,57 +318,20 @@ export class Grid { } if (this.focusBehavior.stateStale()) { - const activeCell = this.focusBehavior.activeCell(); - const activeCoords = this.focusBehavior.activeCoords(); - // Try focus on the same active cell after if a reordering happened. - if (activeCell && this.focusBehavior.focusCell(activeCell)) { + if (this.focusBehavior.focusCell(this.focusBehavior.activeCell()!)) { return true; } // If the active cell is no longer exist, focus on the coordinates instead. - if (this.focusBehavior.focusCoordinates(activeCoords)) { + if (this.focusBehavior.focusCoordinates(this.focusBehavior.activeCoords())) { return true; } - // If the coordinates are no longer valid (e.g. because the row was deleted at the end), - // try to focus on the previous row focusing on the same column. - const maxRow = this.data.maxRowCount() - 1; - const targetRow = Math.min(activeCoords.row, maxRow); - - if (targetRow >= 0) { - // Try same column in the clamped row. - if (this.focusBehavior.focusCoordinates({row: targetRow, col: activeCoords.col})) { - return true; - } - - // Try clamping the column as well. - const colCount = this.data.getColCount(targetRow); - if (colCount !== undefined) { - const targetCol = Math.min(activeCoords.col, colCount - 1); - if ( - targetCol >= 0 && - this.focusBehavior.focusCoordinates({row: targetRow, col: targetCol}) - ) { - return true; - } - } - - // If that fails, try to find ANY cell in that row. - const firstInRow = this.navigationBehavior.peekFirst(targetRow); - if (firstInRow !== undefined && this.focusBehavior.focusCoordinates(firstInRow)) { - return true; - } - } - // If the coordinates no longer valid, go back to the first available cell. - const firstAvailable = this.navigationBehavior.peekFirst(); - if (firstAvailable !== undefined && this.focusBehavior.focusCoordinates(firstAvailable)) { + if (this.focusBehavior.focusCoordinates(this.navigationBehavior.peekFirst()!)) { return true; } - - this.focusBehavior.activeCell.set(undefined); - this.focusBehavior.activeCoords.set({row: -1, col: -1}); } return false; diff --git a/src/aria/private/behaviors/list-focus/list-focus.spec.ts b/src/aria/private/behaviors/list-focus/list-focus.spec.ts index e9bfe199eec8..c37c1e40f629 100644 --- a/src/aria/private/behaviors/list-focus/list-focus.spec.ts +++ b/src/aria/private/behaviors/list-focus/list-focus.spec.ts @@ -100,10 +100,10 @@ describe('List Focus', () => { expect(focusManager.getActiveDescendant()).toBe(focusManager.inputs.items()[1].id()); }); - it('should NOT focus the list element when focusing an item', () => { + it('should focus the list element when focusing an item', () => { const focusSpy = spyOn(focusManager.inputs.element()!, 'focus'); focusManager.focus(focusManager.inputs.items()[1]); - expect(focusSpy).not.toHaveBeenCalled(); + expect(focusSpy).toHaveBeenCalled(); }); }); diff --git a/src/aria/private/behaviors/list-focus/list-focus.ts b/src/aria/private/behaviors/list-focus/list-focus.ts index f2fddbdfac5c..eea98fc70ae9 100644 --- a/src/aria/private/behaviors/list-focus/list-focus.ts +++ b/src/aria/private/behaviors/list-focus/list-focus.ts @@ -18,6 +18,9 @@ export interface ListFocusItem { /** Whether an item is disabled. */ disabled: SignalLike; + + /** The index of the item in the list. */ + index: SignalLike; } /** Represents the required inputs for a collection that contains focusable items. */ @@ -44,15 +47,15 @@ export interface ListFocusInputs { /** Controls focus for a list of items. */ export class ListFocus { /** The last item that was active. */ - readonly prevActiveItem = signal(undefined); + prevActiveItem = signal(undefined); /** The index of the last item that was active. */ - readonly prevActiveIndex = computed(() => { + prevActiveIndex = computed(() => { return this.prevActiveItem() ? this.inputs.items().indexOf(this.prevActiveItem()!) : -1; }); /** The current active index in the list. */ - readonly activeIndex = computed(() => { + activeIndex = computed(() => { return this.inputs.activeItem() ? this.inputs.items().indexOf(this.inputs.activeItem()!) : -1; }); @@ -103,9 +106,9 @@ export class ListFocus { this.inputs.activeItem.set(item); if (opts?.focusElement || opts?.focusElement === undefined) { - if (this.inputs.focusMode() === 'roving') { - item.element()?.focus(); - } + this.inputs.focusMode() === 'roving' + ? item.element()?.focus() + : this.inputs.element()?.focus(); } return true; diff --git a/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts b/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts index 802ba74fd4d7..7ac8fa4ced49 100644 --- a/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts +++ b/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts @@ -52,7 +52,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should wrap (#next)', () => { + it('should wrap', () => { const nav = getNavigation({wrap: signal(true)}); nav.next(); // 0 -> 1 nav.next(); // 1 -> 2 @@ -62,7 +62,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should not wrap (#next)', () => { + it('should not wrap', () => { const nav = getNavigation({wrap: signal(false)}); nav.next(); // 0 -> 1 nav.next(); // 1 -> 2 @@ -72,7 +72,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[4]); }); - it('should skip disabled items (#next)', () => { + it('should skip disabled items', () => { const nav = getNavigation({softDisabled: signal(false)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -80,7 +80,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[2]); }); - it('should not skip disabled items (#next)', () => { + it('should not skip disabled items', () => { const nav = getNavigation({softDisabled: signal(true)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -88,7 +88,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[1]); }); - it('should wrap and skip disabled items (#next)', () => { + it('should wrap and skip disabled items', () => { const nav = getNavigation({ wrap: signal(true), softDisabled: signal(false), @@ -104,7 +104,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should do nothing if other items are disabled (#next)', () => { + it('should do nothing if other items are disabled', () => { const nav = getNavigation({softDisabled: signal(false)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -115,7 +115,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should do nothing if there are no other items to navigate to (#next)', () => { + it('should do nothing if there are no other items to navigate to', () => { const nav = getNavigation({numItems: 1}); nav.next(); // 0 -> 0 expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); @@ -137,19 +137,19 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[2]); }); - it('should wrap (#prev)', () => { + it('should wrap', () => { const nav = getNavigation({wrap: signal(true)}); nav.prev(); // 0 -> 4 expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[4]); }); - it('should not wrap (#prev)', () => { + it('should not wrap', () => { const nav = getNavigation({wrap: signal(false)}); nav.prev(); // 0 -> 0 expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should skip disabled items (#prev)', () => { + it('should skip disabled items', () => { const nav = getNavigation({softDisabled: signal(false)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; @@ -158,7 +158,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should not skip disabled items (#prev)', () => { + it('should not skip disabled items', () => { const nav = getNavigation({softDisabled: signal(true)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; @@ -167,7 +167,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[1]); }); - it('should wrap and skip disabled items (#prev)', () => { + it('should wrap and skip disabled items', () => { const nav = getNavigation({ wrap: signal(true), softDisabled: signal(false), @@ -180,7 +180,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[4]); }); - it('should do nothing if other items are disabled (#prev)', () => { + it('should do nothing if other items are disabled', () => { const nav = getNavigation({ softDisabled: signal(false), }); @@ -193,7 +193,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should do nothing if there are no other items to navigate to (#prev)', () => { + it('should do nothing if there are no other items to navigate to', () => { const nav = getNavigation({numItems: 1}); nav.prev(); // 0 -> 0 expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); @@ -208,7 +208,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[0]); }); - it('should skip disabled items (#first)', () => { + it('should skip disabled items', () => { const nav = getNavigation({softDisabled: signal(false)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; @@ -217,7 +217,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[1]); }); - it('should not skip disabled items (#first)', () => { + it('should not skip disabled items', () => { const nav = getNavigation({softDisabled: signal(true)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; @@ -234,7 +234,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[4]); }); - it('should skip disabled items (#last)', () => { + it('should skip disabled items', () => { const nav = getNavigation({ softDisabled: signal(false), }); @@ -244,7 +244,7 @@ describe('List Navigation', () => { expect(nav.inputs.activeItem()).toBe(nav.inputs.items()[3]); }); - it('should not skip disabled items (#last)', () => { + it('should not skip disabled items', () => { const nav = getNavigation({ softDisabled: signal(true), }); diff --git a/src/aria/private/behaviors/list-selection/list-selection.spec.ts b/src/aria/private/behaviors/list-selection/list-selection.spec.ts index 8093639896ea..b33eac1f0cca 100644 --- a/src/aria/private/behaviors/list-selection/list-selection.spec.ts +++ b/src/aria/private/behaviors/list-selection/list-selection.spec.ts @@ -27,7 +27,7 @@ function getSelection(inputs: TestInputs = {}): ListSelection { it('should select an item', () => { const selection = getSelection(); selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should select multiple options', () => { @@ -65,7 +65,7 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([0, 1]); + expect(selection.inputs.values()).toEqual([0, 1]); }); it('should not select multiple options', () => { @@ -74,30 +74,30 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[1]); selection.select(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); - it('should not select disabled items (#select)', () => { + it('should not select disabled items', () => { const selection = getSelection(); const items = selection.inputs.items() as TestItem[]; items[0].disabled.set(true); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); - it('should not select non-selectable items (#select)', () => { + it('should not select non-selectable items', () => { const selection = getSelection(); const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); - it('should do nothing to already selected items (#select)', () => { + it('should do nothing to already selected items', () => { const selection = getSelection(); selection.select(); // [0] selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); }); @@ -105,7 +105,7 @@ describe('List Selection', () => { it('should deselect an item', () => { const selection = getSelection(); selection.deselect(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should not deselect disabled items', () => { @@ -114,54 +114,54 @@ describe('List Selection', () => { selection.select(); // [0] items[0].disabled.set(true); selection.deselect(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); - it('should not deselect non-selectable items (#deselect)', () => { + it('should not deselect non-selectable items', () => { const selection = getSelection(); const items = selection.inputs.items() as TestItem[]; selection.select(); // [0] items[0].selectable.set(false); selection.deselect(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); }); describe('#toggle', () => { - it('should select an unselected item (#toggle)', () => { + it('should select an unselected item', () => { const selection = getSelection(); selection.toggle(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); - it('should deselect a selected item (#toggle)', () => { + it('should deselect a selected item', () => { const selection = getSelection(); selection.select(); // [0] selection.toggle(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); - it('should not toggle non-selectable items (#toggle)', () => { + it('should not toggle non-selectable items', () => { const selection = getSelection(); const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggle(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); describe('#toggleOne', () => { - it('should select an unselected item (#toggleOne)', () => { + it('should select an unselected item', () => { const selection = getSelection({multi: signal(true)}); selection.toggleOne(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); - it('should deselect a selected item (#toggleOne)', () => { + it('should deselect a selected item', () => { const selection = getSelection({multi: signal(true)}); selection.select(); // [0] selection.toggleOne(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should only leave one item selected', () => { @@ -170,37 +170,37 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[1]); selection.toggleOne(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); - it('should not toggle non-selectable items (#toggleOne)', () => { + it('should not toggle non-selectable items', () => { const selection = getSelection(); const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggleOne(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); describe('#selectAll', () => { - it('should select all items (#selectAll)', () => { + it('should select all items', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); - expect(selection.inputs.value()).toEqual([0, 1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 1, 2, 3, 4]); }); it('should do nothing if a list is not multiselectable', () => { const selection = getSelection({multi: signal(false)}); selection.selectAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); - it('should not select non-selectable items (#selectAll)', () => { + it('should not select non-selectable items', () => { const selection = getSelection({multi: signal(true)}); const items = selection.inputs.items() as TestItem[]; items[1].selectable.set(false); selection.selectAll(); - expect(selection.inputs.value()).toEqual([0, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 2, 3, 4]); }); }); @@ -209,38 +209,38 @@ describe('List Selection', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); // [0, 1, 2, 3, 4] selection.deselectAll(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should deselect items that are not in the list', () => { const selection = getSelection({multi: signal(true)}); - selection.inputs.value.update(() => [5]); + selection.inputs.values.update(() => [5]); selection.deselectAll(); - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); - it('should not deselect non-selectable items (#deselectAll)', () => { + it('should not deselect non-selectable items', () => { const selection = getSelection({multi: signal(true)}); const items = selection.inputs.items() as TestItem[]; selection.selectAll(); // [0, 1, 2, 3, 4] items[1].selectable.set(false); selection.deselectAll(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); }); describe('#toggleAll', () => { - it('should select all items (#toggleAll)', () => { + it('should select all items', () => { const selection = getSelection({multi: signal(true)}); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([0, 1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 1, 2, 3, 4]); }); it('should deselect all if all items are selected', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should ignore disabled items when determining if all items are selected', () => { @@ -248,9 +248,9 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].disabled.set(true); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([1, 2, 3, 4]); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should ignore non-selectable items when determining if all items are selected', () => { @@ -258,9 +258,9 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([1, 2, 3, 4]); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); @@ -271,31 +271,31 @@ describe('List Selection', () => { selection.selectOne(); // [0] selection.inputs.focusManager.focus(items[1]); selection.selectOne(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); - it('should not select disabled items (#selectOne)', () => { + it('should not select disabled items', () => { const selection = getSelection({multi: signal(true)}); const items = selection.inputs.items() as TestItem[]; items[0].disabled.set(true); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); - it('should not select non-selectable items (#selectOne)', () => { + it('should not select non-selectable items', () => { const selection = getSelection({multi: signal(true)}); const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.selectOne(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); - it('should do nothing to already selected items (#selectOne)', () => { + it('should do nothing to already selected items', () => { const selection = getSelection({multi: signal(true)}); selection.selectOne(); // [0] selection.selectOne(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should do nothing if the current active item is disabled', () => { @@ -304,12 +304,12 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); selection.inputs.focusManager.focus(items[0]); items[0].disabled.set(true); selection.selectOne(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); it('should not select an item if the list is not multiselectable and not all items are deselected', () => { @@ -318,12 +318,12 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); items[1].disabled.set(true); selection.inputs.focusManager.focus(items[2]); selection.selectOne(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); }); @@ -334,7 +334,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([0, 1, 2]); + expect(selection.inputs.values()).toEqual([0, 1, 2]); }); it('should select all items from an anchor at a higher index', () => { @@ -346,7 +346,7 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.selectRange(); // [3, 2, 1] - expect(selection.inputs.value()).toEqual([3, 2, 1]); + expect(selection.inputs.values()).toEqual([3, 2, 1]); }); it('should deselect items within the range when the range is changed', () => { @@ -355,15 +355,15 @@ describe('List Selection', () => { selection.inputs.activeItem.set(items[2]); selection.select(); // [2] - expect(selection.inputs.value()).toEqual([2]); + expect(selection.inputs.values()).toEqual([2]); selection.inputs.focusManager.focus(items[4]); selection.selectRange(); // [2, 3, 4] - expect(selection.inputs.value()).toEqual([2, 3, 4]); + expect(selection.inputs.values()).toEqual([2, 3, 4]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [2, 1, 0] - expect(selection.inputs.value()).toEqual([2, 1, 0]); + expect(selection.inputs.values()).toEqual([2, 1, 0]); }); it('should not select a disabled item', () => { @@ -372,15 +372,15 @@ describe('List Selection', () => { items[1].disabled.set(true); selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); selection.inputs.focusManager.focus(items[1]); selection.selectRange(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 2] - expect(selection.inputs.value()).toEqual([0, 2]); + expect(selection.inputs.values()).toEqual([0, 2]); }); it('should not select a non-selectable item', () => { @@ -390,7 +390,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 2] - expect(selection.inputs.value()).toEqual([0, 2]); + expect(selection.inputs.values()).toEqual([0, 2]); }); it('should not deselect a disabled item', () => { @@ -401,15 +401,15 @@ describe('List Selection', () => { items[1].disabled.set(true); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([1, 0, 2]); + expect(selection.inputs.values()).toEqual([1, 0, 2]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); }); it('should not deselect a non-selectable item', () => { @@ -418,13 +418,13 @@ describe('List Selection', () => { selection.select(items[1]); // [1] items[1].selectable.set(false); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([1, 0, 2]); + expect(selection.inputs.values()).toEqual([1, 0, 2]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); }); }); @@ -434,10 +434,10 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; selection.inputs.focusManager.focus(items[2]); selection.beginRangeSelection(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); selection.inputs.focusManager.focus(items[4]); selection.selectRange(); // [2, 3, 4] - expect(selection.inputs.value()).toEqual([2, 3, 4]); + expect(selection.inputs.values()).toEqual([2, 3, 4]); }); it('should be able to select a range starting on a disabled item', () => { @@ -447,7 +447,7 @@ describe('List Selection', () => { selection.beginRangeSelection(0); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); - expect(selection.inputs.value()).toEqual([1, 2]); + expect(selection.inputs.values()).toEqual([1, 2]); }); }); }); diff --git a/src/aria/private/behaviors/list-selection/list-selection.ts b/src/aria/private/behaviors/list-selection/list-selection.ts index 8b22b28110ac..c5b08f17a15b 100644 --- a/src/aria/private/behaviors/list-selection/list-selection.ts +++ b/src/aria/private/behaviors/list-selection/list-selection.ts @@ -24,7 +24,7 @@ export interface ListSelectionInputs, V> extends multi: SignalLike; /** The current value of the list selection. */ - value: WritableSignalLike; + values: WritableSignalLike; /** The selection strategy used by the list. */ selectionMode: SignalLike<'follow' | 'explicit'>; @@ -33,14 +33,14 @@ export interface ListSelectionInputs, V> extends /** Controls selection for a list of items. */ export class ListSelection, V> { /** The start index to use for range selection. */ - readonly rangeStartIndex = signal(0); + rangeStartIndex = signal(0); /** The end index to use for range selection. */ - readonly rangeEndIndex = signal(0); + rangeEndIndex = signal(0); /** The currently selected items. */ - readonly selectedItems = computed(() => - this.inputs.items().filter(item => this.inputs.value().includes(item.value())), + selectedItems = computed(() => + this.inputs.items().filter(item => this.inputs.values().includes(item.value())), ); constructor(readonly inputs: ListSelectionInputs & {focusManager: ListFocus}) {} @@ -54,7 +54,7 @@ export class ListSelection, V> { item.disabled() || !item.selectable() || !this.inputs.focusManager.isFocusable(item as T) || - this.inputs.value().includes(item.value()) + this.inputs.values().includes(item.value()) ) { return; } @@ -67,7 +67,7 @@ export class ListSelection, V> { if (opts.anchor) { this.beginRangeSelection(index); } - this.inputs.value.update(values => values.concat(item.value())); + this.inputs.values.update(values => values.concat(item.value())); } /** Deselects the item at the current active index. */ @@ -75,7 +75,7 @@ export class ListSelection, V> { item = item ?? this.inputs.focusManager.inputs.activeItem(); if (item && !item.disabled() && item.selectable()) { - this.inputs.value.update(values => values.filter(value => value !== item.value())); + this.inputs.values.update(values => values.filter(value => value !== item.value())); } } @@ -83,7 +83,7 @@ export class ListSelection, V> { toggle(item?: ListSelectionItem) { item = item ?? this.inputs.focusManager.inputs.activeItem(); if (item) { - this.inputs.value().includes(item.value()) ? this.deselect(item) : this.select(item); + this.inputs.values().includes(item.value()) ? this.deselect(item) : this.select(item); } } @@ -91,7 +91,7 @@ export class ListSelection, V> { toggleOne() { const item = this.inputs.focusManager.inputs.activeItem(); if (item) { - this.inputs.value().includes(item.value()) ? this.deselect() : this.selectOne(); + this.inputs.values().includes(item.value()) ? this.deselect() : this.selectOne(); } } @@ -123,12 +123,12 @@ export class ListSelection, V> { // inverse (and more common) effect of keeping enabled items selected when they aren't in the // list. - for (const value of this.inputs.value()) { + for (const value of this.inputs.values()) { const item = this.inputs.items().find(i => i.value() === value); item ? this.deselect(item) - : this.inputs.value.update(values => values.filter(v => v !== value)); + : this.inputs.values.update(values => values.filter(v => v !== value)); } } @@ -142,7 +142,7 @@ export class ListSelection, V> { .filter(i => !i.disabled() && i.selectable() && this.inputs.focusManager.isFocusable(i)) .map(i => i.value()); - selectableValues.every(i => this.inputs.value().includes(i)) + selectableValues.every(i => this.inputs.values().includes(i)) ? this.deselectAll() : this.selectAll(); } @@ -156,7 +156,7 @@ export class ListSelection, V> { this.deselectAll(); - if (this.inputs.value().length > 0 && !this.inputs.multi()) { + if (this.inputs.values().length > 0 && !this.inputs.multi()) { return; } diff --git a/src/aria/private/behaviors/list-typeahead/list-typeahead.ts b/src/aria/private/behaviors/list-typeahead/list-typeahead.ts index 7719dd0edfa1..048a2ea99d5b 100644 --- a/src/aria/private/behaviors/list-typeahead/list-typeahead.ts +++ b/src/aria/private/behaviors/list-typeahead/list-typeahead.ts @@ -33,16 +33,16 @@ export class ListTypeahead { timeout?: ReturnType | undefined; /** The focus controller of the parent list. */ - readonly focusManager: ListFocus; + focusManager: ListFocus; /** Whether the user is actively typing a typeahead search query. */ - readonly isTyping = computed(() => this._query().length > 0); + isTyping = computed(() => this._query().length > 0); /** Keeps track of the characters that typeahead search is being called with. */ - private readonly _query = signal(''); + private _query = signal(''); /** The index where that the typeahead search was initiated from. */ - private readonly _startIndex = signal(undefined); + private _startIndex = signal(undefined); constructor(readonly inputs: ListTypeaheadInputs & {focusManager: ListFocus}) { this.focusManager = inputs.focusManager; diff --git a/src/aria/private/behaviors/list/list.spec.ts b/src/aria/private/behaviors/list/list.spec.ts index ab1375318432..39a6c98eb138 100644 --- a/src/aria/private/behaviors/list/list.spec.ts +++ b/src/aria/private/behaviors/list/list.spec.ts @@ -22,7 +22,7 @@ type TestList = List, V>; describe('List Behavior', () => { function getList(inputs: Partial> & Pick, 'items'>): TestList { return new List({ - value: inputs.value ?? signal([]), + values: inputs.values ?? signal([]), activeItem: signal(undefined), typeaheadDelay: inputs.typeaheadDelay ?? signal(500), wrap: inputs.wrap ?? signal(true), @@ -38,22 +38,22 @@ describe('List Behavior', () => { }); } - function getItems(value: V[]): TestItem[] { - return value.map((val, index) => ({ - value: signal(val), + function getItems(values: V[]): TestItem[] { + return values.map((value, index) => ({ + value: signal(value), id: signal(`item-${index}`), element: signal(document.createElement('div')), disabled: signal(false), selectable: signal(true), - searchTerm: signal(String(val)), + searchTerm: signal(String(value)), index: signal(index), })); } - function getListAndItems(value: V[], inputs: Partial> = {}) { + function getListAndItems(values: V[], inputs: Partial> = {}) { const items = signal[]>([]); const list = getList({...inputs, items}); - items.set(getItems(value)); + items.set(getItems(values)); list.inputs.activeItem.set(list.inputs.items()[0]); return {list, items: items()}; } @@ -123,11 +123,11 @@ describe('List Behavior', () => { list = patterns.list; }); - it('should report disabled state (softDisabled: false)', () => { + it('should report disabled state', () => { expect(list.disabled()).toBe(true); }); - it('should not change active index on navigation (softDisabled: false)', () => { + it('should not change active index on navigation', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); list.next(); expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); @@ -135,12 +135,12 @@ describe('List Behavior', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); }); - it('should not select items (softDisabled: false)', () => { + it('should not select items', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); - it('should have a tab index of 0 (softDisabled: false)', () => { + it('should have a tab index of 0', () => { expect(list.tabIndex()).toBe(0); }); }); @@ -153,11 +153,11 @@ describe('List Behavior', () => { list = patterns.list; }); - it('should report disabled state (softDisabled: true)', () => { + it('should report disabled state', () => { expect(list.disabled()).toBe(true); }); - it('should not change active index on navigation (softDisabled: true)', () => { + it('should not change active index on navigation', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); list.next(); expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); @@ -165,12 +165,12 @@ describe('List Behavior', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); }); - it('should not select items (softDisabled: true)', () => { + it('should not select items', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); - it('should have a tab index of 0 (softDisabled: true)', () => { + it('should have a tab index of 0', () => { expect(list.tabIndex()).toBe(0); }); }); @@ -287,48 +287,48 @@ describe('List Behavior', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), multi: signal(false), }); list = patterns.list; items = patterns.items; }); - it('should not select when navigating (single select)', () => { + it('should not select when navigating', () => { list.next(); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should select an item when navigating with selectOne:true', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); }); it('should not select a non-selectable item when navigating with selectOne:true', () => { items[1].selectable.set(false); list.next({selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should toggle an item when navigating with toggle:true', () => { list.goto(items[1], {selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); list.goto(items[1], {toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should not toggle a non-selectable item when navigating with toggle:true', () => { items[1].selectable.set(false); list.goto(items[1], {toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should only allow one selected item', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Banana']); + expect(list.inputs.values()).toEqual(['Banana']); }); }); @@ -338,66 +338,66 @@ describe('List Behavior', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), multi: signal(true), }); list = patterns.list; items = patterns.items; }); - it('should not select when navigating (multi select)', () => { + it('should not select when navigating', () => { list.next(); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should select an item with toggle:true', () => { list.next({toggle: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); }); it('should not select a non-selectable item with toggle:true', () => { items[1].selectable.set(false); list.next({toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should allow multiple selected items', () => { list.next({toggle: true}); list.next({toggle: true}); - expect(list.inputs.value()).toEqual(['Apricot', 'Banana']); + expect(list.inputs.values()).toEqual(['Apricot', 'Banana']); }); it('should select a range of items with selectRange:true', () => { list.anchor(0); list.next({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot']); list.next({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); list.prev({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot']); list.prev({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple']); + expect(list.inputs.values()).toEqual(['Apple']); }); it('should not wrap when range selecting', () => { list.anchor(0); list.prev({selectRange: true}); expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should not select disabled items in a range', () => { items[1].disabled.set(true); list.anchor(0); list.goto(items[3], {selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry']); + expect(list.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry']); }); it('should not select non-selectable items in a range', () => { items[1].selectable.set(false); list.anchor(0); list.goto(items[3], {selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry']); + expect(list.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry']); }); }); }); @@ -437,14 +437,14 @@ describe('List Behavior', () => { it('should select an item via typeahead', () => { const {list} = getDefaultPatterns({multi: signal(false)}); list.search('b', {selectOne: true}); - expect(list.inputs.value()).toEqual(['Banana']); + expect(list.inputs.values()).toEqual(['Banana']); }); it('should not select a non-selectable item via typeahead', () => { const {list, items} = getDefaultPatterns({multi: signal(false)}); items[2].selectable.set(false); // 'Banana' list.search('b', {selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); }); }); diff --git a/src/aria/private/behaviors/list/list.ts b/src/aria/private/behaviors/list/list.ts index 7378ba27ae13..2fd702f481ff 100644 --- a/src/aria/private/behaviors/list/list.ts +++ b/src/aria/private/behaviors/list/list.ts @@ -50,28 +50,28 @@ export type ListInputs, V> = ListFocusInputs & /** Controls the state of a list. */ export class List, V> { /** Controls navigation for the list. */ - readonly navigationBehavior: ListNavigation; + navigationBehavior: ListNavigation; /** Controls selection for the list. */ - readonly selectionBehavior: ListSelection; + selectionBehavior: ListSelection; /** Controls typeahead for the list. */ - readonly typeaheadBehavior: ListTypeahead; + typeaheadBehavior: ListTypeahead; /** Controls focus for the list. */ - readonly focusBehavior: ListFocus; + focusBehavior: ListFocus; /** Whether the list is disabled. */ - readonly disabled = computed(() => this.focusBehavior.isListDisabled()); + disabled = computed(() => this.focusBehavior.isListDisabled()); /** The id of the current active item. */ - readonly activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); + activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); /** The tab index of the list. */ - readonly tabIndex = computed(() => this.focusBehavior.getListTabIndex()); + tabIndex = computed(() => this.focusBehavior.getListTabIndex()); /** The index of the currently active item in the list. */ - readonly activeIndex = computed(() => this.focusBehavior.activeIndex()); + activeIndex = computed(() => this.focusBehavior.activeIndex()); /** * The uncommitted index for selecting a range of options. @@ -85,10 +85,10 @@ export class List, V> { * In other words, "rangeStartIndex" is only set when a user commits to starting a range selection * while "anchorIndex" is set whenever a user indicates they may be starting a range selection. */ - private readonly _anchorIndex = signal(0); + private _anchorIndex = signal(0); /** Whether the list should wrap. Used to disable wrapping while range selecting. */ - private readonly _wrap = signal(true); + private _wrap = signal(true); constructor(readonly inputs: ListInputs) { this.focusBehavior = new ListFocus(inputs); @@ -229,9 +229,6 @@ export class List, V> { if (moved) { this.updateSelection(opts); - if (!opts?.selectRange) { - this.anchor(this.activeIndex()); - } } this._wrap.set(true); diff --git a/src/aria/private/behaviors/signal-like/signal-like.ts b/src/aria/private/behaviors/signal-like/signal-like.ts index 36081d4100b8..ee11e6ff9fa9 100644 --- a/src/aria/private/behaviors/signal-like/signal-like.ts +++ b/src/aria/private/behaviors/signal-like/signal-like.ts @@ -41,6 +41,8 @@ export function convertGetterSetterToWritableSignalLike( export function computed(computation: () => T): SignalLike { const computed = createComputed(computation); + // TODO: Remove the `toString` after https://github.com/angular/angular/pull/65948 is merged. + computed.toString = () => `[Computed: ${computed()}]`; computed[SIGNAL].debugName = ''; return computed; } diff --git a/src/aria/private/behaviors/tree/tree.spec.ts b/src/aria/private/behaviors/tree/tree.spec.ts index 721d4cca4750..7083cf2dde0d 100644 --- a/src/aria/private/behaviors/tree/tree.spec.ts +++ b/src/aria/private/behaviors/tree/tree.spec.ts @@ -43,7 +43,7 @@ describe('Tree Behavior', () => { return new Tree, V>({ ...focusInputs, - value: signal([]), + values: signal([]), multi: signal(false), multiExpandable: signal(true), selectionMode: signal('follow'), @@ -140,12 +140,12 @@ describe('Tree Behavior', () => { }); describe('with disabled: true and softDisabled is false', () => { - it('should report disabled state (softDisabled: false)', () => { + it('should report disabled state', () => { const {tree} = getDefaultPatterns({disabled: signal(true), softDisabled: signal(false)}); expect(tree.disabled()).toBe(true); }); - it('should not change active index on navigation (softDisabled: false)', () => { + it('should not change active index on navigation', () => { const {tree} = getDefaultPatterns({disabled: signal(true), softDisabled: signal(false)}); expect(tree.inputs.activeItem()).toBe(tree.inputs.items()[0]); tree.next(); @@ -154,25 +154,25 @@ describe('Tree Behavior', () => { expect(tree.inputs.activeItem()).toBe(tree.inputs.items()[0]); }); - it('should not select items (softDisabled: false)', () => { + it('should not select items', () => { const {tree} = getDefaultPatterns({disabled: signal(true), softDisabled: signal(false)}); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); - it('should have a tab index of 0 (softDisabled: false)', () => { + it('should have a tab index of 0', () => { const {tree} = getDefaultPatterns({disabled: signal(true), softDisabled: signal(false)}); expect(tree.tabIndex()).toBe(0); }); }); describe('with disabled: true', () => { - it('should report disabled state (softDisabled: true)', () => { + it('should report disabled state', () => { const {tree} = getDefaultPatterns({disabled: signal(true)}); expect(tree.disabled()).toBe(true); }); - it('should not change active index on navigation (softDisabled: true)', () => { + it('should not change active index on navigation', () => { const {tree} = getDefaultPatterns({disabled: signal(true)}); expect(tree.inputs.activeItem()).toBe(tree.inputs.items()[0]); tree.next(); @@ -181,13 +181,13 @@ describe('Tree Behavior', () => { expect(tree.inputs.activeItem()).toBe(tree.inputs.items()[0]); }); - it('should not select items (softDisabled: true)', () => { + it('should not select items', () => { const {tree} = getDefaultPatterns({disabled: signal(true)}); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); - it('should have a tab index of 0 (softDisabled: true)', () => { + it('should have a tab index of 0', () => { const {tree} = getDefaultPatterns({disabled: signal(true)}); expect(tree.tabIndex()).toBe(0); }); @@ -360,7 +360,7 @@ describe('Tree Behavior', () => { expect(tree.inputs.activeItem()).toBe(items[0]); // Stays same }); - it('should skip invisible items (subset navigation)', () => { + it('should skipping invisible items (subset navigation)', () => { const {tree, items} = getDefaultPatterns(); // 0 -> p // 1 -> c (invisible) @@ -378,113 +378,113 @@ describe('Tree Behavior', () => { describe('Selection', () => { describe('single select', () => { - it('should not select when navigating (single select)', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(false)}); + it('should not select when navigating', () => { + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(false)}); tree.next(); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should select an item when navigating with selectOne:true', () => { - const {tree} = getTreeAndItems([0, 1], {value: signal([]), multi: signal(false)}); + const {tree} = getTreeAndItems([0, 1], {values: signal([]), multi: signal(false)}); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([1]); + expect(tree.inputs.values()).toEqual([1]); }); it('should not select a non-selectable item when navigating with selectOne:true', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(false)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(false)}); items[1].selectable.set(false); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should toggle an item when navigating with toggle:true', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(false)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(false)}); tree.goto(items[1], {selectOne: true}); - expect(tree.inputs.value()).toEqual([1]); + expect(tree.inputs.values()).toEqual([1]); tree.goto(items[1], {toggle: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not toggle a non-selectable item when navigating with toggle:true', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(false)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(false)}); items[1].selectable.set(false); tree.goto(items[1], {toggle: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should only allow one selected item', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(false)}); + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(false)}); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([1]); + expect(tree.inputs.values()).toEqual([1]); tree.next({selectOne: true}); - expect(tree.inputs.value()).toEqual([2]); + expect(tree.inputs.values()).toEqual([2]); }); }); describe('multi select', () => { - it('should not select when navigating (multi select)', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + it('should not select when navigating', () => { + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(true)}); tree.next(); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should select an item with toggle:true', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(true)}); tree.next({toggle: true}); - expect(tree.inputs.value()).toEqual([1]); + expect(tree.inputs.values()).toEqual([1]); }); it('should not select a non-selectable item with toggle:true', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(true)}); items[1].selectable.set(false); tree.next({toggle: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should allow multiple selected items', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(true)}); tree.next({toggle: true}); tree.next({toggle: true}); - expect(tree.inputs.value()).toEqual([1, 2]); + expect(tree.inputs.values()).toEqual([1, 2]); }); it('should select a range of items with selectRange:true', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(true)}); tree.anchor(0); tree.next({selectRange: true}); - expect(tree.inputs.value()).toEqual([0, 1]); // Apple (0), Apricot (1) + expect(tree.inputs.values()).toEqual([0, 1]); // Apple (0), Apricot (1) tree.next({selectRange: true}); - expect(tree.inputs.value()).toEqual([0, 1, 2]); + expect(tree.inputs.values()).toEqual([0, 1, 2]); tree.prev({selectRange: true}); - expect(tree.inputs.value()).toEqual([0, 1]); + expect(tree.inputs.values()).toEqual([0, 1]); tree.prev({selectRange: true}); - expect(tree.inputs.value()).toEqual([0]); + expect(tree.inputs.values()).toEqual([0]); }); it('should not wrap when range selecting', () => { - const {tree} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree} = getDefaultPatterns({values: signal([]), multi: signal(true)}); tree.anchor(0); tree.prev({selectRange: true}); expect(tree.inputs.activeItem()).toBe(tree.inputs.items()[0]); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not select disabled items in a range', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(true)}); items[1].disabled.set(true); tree.anchor(0); tree.goto(items[3], {selectRange: true}); - expect(tree.inputs.value()).toEqual([0, 2, 3]); // Skips 1 + expect(tree.inputs.values()).toEqual([0, 2, 3]); // Skips 1 }); it('should not select non-selectable items in a range', () => { - const {tree, items} = getDefaultPatterns({value: signal([]), multi: signal(true)}); + const {tree, items} = getDefaultPatterns({values: signal([]), multi: signal(true)}); items[1].selectable.set(false); tree.anchor(0); tree.goto(items[3], {selectRange: true}); - expect(tree.inputs.value()).toEqual([0, 2, 3]); // Skips 1 + expect(tree.inputs.values()).toEqual([0, 2, 3]); // Skips 1 }); }); }); @@ -534,14 +534,14 @@ describe('Tree Behavior', () => { it('should select an item via typeahead', () => { const {tree} = getTreeAndItems(['Apple', 'Banana'], {multi: signal(false)}); tree.search('b', {selectOne: true}); - expect(tree.inputs.value()).toEqual(['Banana']); + expect(tree.inputs.values()).toEqual(['Banana']); }); it('should not select a non-selectable item via typeahead', () => { const {tree, items} = getTreeAndItems(['Apple', 'Banana'], {multi: signal(false)}); items[1].selectable.set(false); tree.search('b', {selectOne: true}); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); }); diff --git a/src/aria/private/behaviors/tree/tree.ts b/src/aria/private/behaviors/tree/tree.ts index e460be1f8b36..7dd6040c2fd2 100644 --- a/src/aria/private/behaviors/tree/tree.ts +++ b/src/aria/private/behaviors/tree/tree.ts @@ -61,37 +61,37 @@ class TreeListFocus, V> extends ListFocus { /** Controls the state of a tree. */ export class Tree, V> { /** Controls navigation for the tree. */ - readonly navigationBehavior: ListNavigation; + navigationBehavior: ListNavigation; /** Controls selection for the tree. */ - readonly selectionBehavior: ListSelection; + selectionBehavior: ListSelection; /** Controls typeahead for the tree. */ - readonly typeaheadBehavior: ListTypeahead; + typeaheadBehavior: ListTypeahead; /** Controls focus for the tree. */ - readonly focusBehavior: ListFocus; + focusBehavior: ListFocus; /** Controls expansion for the tree. */ - readonly expansionBehavior: ListExpansion; + expansionBehavior: ListExpansion; /** Whether the tree is disabled. */ - readonly disabled = computed(() => this.focusBehavior.isListDisabled()); + disabled = computed(() => this.focusBehavior.isListDisabled()); /** The id of the current active item. */ - readonly activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); + activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); /** The tab index of the tree. */ - readonly tabIndex = computed(() => this.focusBehavior.getListTabIndex()); + tabIndex = computed(() => this.focusBehavior.getListTabIndex()); /** The index of the currently active item in the tree (within the flattened list). */ - readonly activeIndex = computed(() => this.focusBehavior.activeIndex()); + activeIndex = computed(() => this.focusBehavior.activeIndex()); /** The uncommitted index for selecting a range of options. */ - private readonly _anchorIndex = signal(0); + private _anchorIndex = signal(0); /** Whether the list should wrap. */ - private readonly _wrap = signal(true); + private _wrap = signal(true); constructor(readonly inputs: TreeInputs) { this.focusBehavior = new TreeListFocus(inputs); @@ -260,11 +260,9 @@ export class Tree, V> { item ??= this.inputs.activeItem(); if (!item) return; - // If the item has a parent, get all of the parent's children. - // Otherwise, it is a root item, and get all other root items. const parent = item.parent?.(); + // TODO: This assumes that items without a parent are root items. const siblings = parent ? parent.children?.() : this.inputs.items().filter(i => !i.parent?.()); - siblings?.forEach(s => this.expand(s)); } diff --git a/src/aria/private/combobox/BUILD.bazel b/src/aria/private/combobox/BUILD.bazel index 4b8ec2562657..61c73f8a68a8 100644 --- a/src/aria/private/combobox/BUILD.bazel +++ b/src/aria/private/combobox/BUILD.bazel @@ -11,7 +11,6 @@ ts_project( deps = [ "//:node_modules/@angular/core", "//src/aria/private/behaviors/event-manager", - "//src/aria/private/behaviors/expansion", "//src/aria/private/behaviors/list", "//src/aria/private/behaviors/signal-like", ], diff --git a/src/aria/private/combobox/combobox.spec.ts b/src/aria/private/combobox/combobox.spec.ts index 844864f7b94a..bff407d696c5 100644 --- a/src/aria/private/combobox/combobox.spec.ts +++ b/src/aria/private/combobox/combobox.spec.ts @@ -1,298 +1,987 @@ -import {ComboboxPattern, ComboboxPopupPattern} from './combobox'; -import {signal} from '../behaviors/signal-like/signal-like'; +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ComboboxInputs, ComboboxPattern} from './combobox'; +import {OptionPattern} from '../listbox/option'; +import {ComboboxListboxPattern} from '../listbox/combobox-listbox'; import {createKeyboardEvent} from '@angular/cdk/testing/private'; +import {SignalLike, signal, WritableSignalLike} from '../behaviors/signal-like/signal-like'; +import {ModifierKeys} from '@angular/cdk/testing'; +import {TreeItemPattern} from '../tree/tree'; +import {ComboboxTreePattern} from '../tree/combobox-tree'; + +// Test types +type TestOption = OptionPattern & { + disabled: WritableSignalLike; +}; + +type TestInputs = { + readonly [K in keyof ComboboxInputs]: WritableSignalLike< + ComboboxInputs[K] extends SignalLike ? T : never + >; +}; + +type TreeItemData = {value: string; children?: TreeItemData[]}; + +// Keyboard event helpers +const up = () => createKeyboardEvent('keydown', 38, 'ArrowUp'); +const down = () => createKeyboardEvent('keydown', 40, 'ArrowDown'); +const home = () => createKeyboardEvent('keydown', 36, 'Home'); +const end = () => createKeyboardEvent('keydown', 35, 'End'); +const enter = () => createKeyboardEvent('keydown', 13, 'Enter'); +const escape = () => createKeyboardEvent('keydown', 27, 'Escape'); +const right = () => createKeyboardEvent('keydown', 39, 'ArrowRight'); +const left = () => createKeyboardEvent('keydown', 37, 'ArrowLeft'); + +function clickOption(options: OptionPattern[], index: number, mods?: ModifierKeys) { + return { + target: options[index].element(), + shiftKey: mods?.shift, + ctrlKey: mods?.control, + } as unknown as PointerEvent; +} + +function clickTreeItem(items: TreeItemPattern[], index: number, mods?: ModifierKeys) { + return { + target: items[index].element(), + shiftKey: mods?.shift, + ctrlKey: mods?.control, + } as unknown as PointerEvent; +} + +function clickInput(inputEl: HTMLInputElement) { + return {target: inputEl} as unknown as PointerEvent; +} + +function _type( + text: string, + inputEl: HTMLInputElement, + combobox: ComboboxPattern, + allOptions: TestOption[] | TreeItemPattern[], + popup: ComboboxListboxPattern | ComboboxTreePattern, + firstMatch: WritableSignalLike, + backspace = false, +) { + combobox.onFocusIn(); + inputEl.value = text; + combobox.onInput( + backspace + ? new InputEvent('input', {inputType: 'deleteContentBackward'}) + : new InputEvent('input'), + ); + const options = allOptions.filter(o => o.searchTerm().startsWith(text)); + if (popup instanceof ComboboxListboxPattern) { + (popup.inputs.items as WritableSignalLike).set(options); + } else if (popup instanceof ComboboxTreePattern) { + (popup.inputs.items as WritableSignalLike).set(options); + // Auto-expand parents of matched items so they are visible + options.forEach(option => { + if (option instanceof TreeItemPattern) { + let parent = option.parent(); + while (parent instanceof TreeItemPattern) { + (parent.expanded as WritableSignalLike).set(true); + parent = parent.parent(); + } + } + }); + } + firstMatch.set(options[0]?.value()); + combobox.onFilter(); +} + +function getComboboxPattern( + inputs: Partial<{ + [K in keyof TestInputs]: TestInputs[K] extends WritableSignalLike ? T : never; + }> = {}, +) { + const containerEl = signal(document.createElement('div')); + const inputEl = signal(document.createElement('input')); + containerEl()?.appendChild(inputEl()!); + const firstMatch = signal(undefined); + const inputValue = signal(''); + + const combobox = new ComboboxPattern({ + disabled: signal(inputs.disabled ?? false), + readonly: signal(inputs.readonly ?? false), + textDirection: signal(inputs.textDirection ?? 'ltr'), + popupControls: signal(undefined), // will be set later + inputEl, + containerEl, + filterMode: signal(inputs.filterMode ?? 'manual'), + firstMatch, + inputValue, + alwaysExpanded: signal(false), + }); + + return {combobox, inputEl, containerEl, firstMatch, inputValue}; +} + +function getListboxPattern( + combobox: ComboboxPattern, + values: string[], + initialValue?: string, +) { + const options = signal([]); + + const listbox = new ComboboxListboxPattern({ + id: signal('listbox-1'), + items: options, + values: signal(initialValue ? [initialValue] : []), + combobox: signal(combobox) as any, + activeItem: signal(undefined), + typeaheadDelay: signal(500), + wrap: signal(true), + readonly: signal(false), + disabled: signal(false), + softDisabled: signal(true), + multi: signal(false), + focusMode: signal('activedescendant'), + textDirection: signal('ltr'), + orientation: signal('vertical'), + selectionMode: signal('explicit'), + element: signal(document.createElement('div')), + }); + + options.set( + values.map((v, index) => { + const element = document.createElement('div'); + element.role = 'option'; + return new OptionPattern({ + value: signal(v), + id: signal(`option-${index}`), + disabled: signal(false), + searchTerm: signal(v), + listbox: signal(listbox), + element: signal(element), + }) as TestOption; + }), + ); + + return {listbox, options}; +} + +function getTreePattern( + combobox: ComboboxPattern, string>, + data: TreeItemData[], + initialValue?: string, +) { + const items = signal[]>([]); + + const tree = new ComboboxTreePattern({ + id: signal('tree-1'), + items, + values: signal(initialValue ? [initialValue] : []), + combobox: signal(combobox) as any, + activeItem: signal(undefined), + typeaheadDelay: signal(500), + wrap: signal(true), + disabled: signal(false), + softDisabled: signal(true), + multi: signal(false), + focusMode: signal('activedescendant'), + textDirection: signal('ltr'), + orientation: signal('vertical'), + selectionMode: signal('explicit'), + element: signal(document.createElement('div')), + nav: signal(false), + currentType: signal('false'), + }); + + class TestTreeItemPattern extends TreeItemPattern {} -describe('ComboboxPattern', () => { - function setup( + // Recursive function to create tree items + function createTreeItems( + data: TreeItemData[], + parent: TreeItemPattern | ComboboxTreePattern, + ) { + return data.map((node, index) => { + const element = document.createElement('div'); + element.role = 'treeitem'; + const treeItem = new TestTreeItemPattern({ + value: signal(node.value), + id: signal('tree-item-' + tree.inputs.items().length), + disabled: signal(false), + selectable: signal(true), + expanded: signal(false), + searchTerm: signal(node.value), + tree: signal(tree), + parent: signal(parent), + element: signal(element), + hasChildren: signal(!!node.children), + children: signal([]), + }); + + (tree.inputs.items as WritableSignalLike[]>).update(items => + items.concat(treeItem), + ); + + if (node.children) { + const children = createTreeItems(node.children, treeItem); + (treeItem.inputs.children as WritableSignalLike[]>).set(children); + } + + return treeItem; + }); + } + + createTreeItems(data, tree); + return {tree, items}; +} + +describe('Combobox with Listbox Pattern', () => { + function getPatterns( inputs: Partial<{ - disabled: boolean; - readonly: boolean; - alwaysExpanded: boolean; - inlineSuggestion: string; - popupType: 'listbox' | 'tree' | 'grid' | 'dialog'; + [K in keyof TestInputs]: TestInputs[K] extends WritableSignalLike ? T : never; }> = {}, ) { - const element = document.createElement('input'); - const value = signal(''); - const expanded = signal(false); - const alwaysExpanded = signal(inputs.alwaysExpanded ?? false); - const disabled = signal(inputs.disabled ?? false); - const readonly = signal(inputs.readonly ?? false); - const inlineSuggestion = signal(inputs.inlineSuggestion); - - // Mock a generic popup pattern - const popupId = signal('popup-1'); - const activeDescendant = signal('item-1'); - const controlTarget = document.createElement('div'); - const popupType = signal<'listbox' | 'tree' | 'grid' | 'dialog'>(inputs.popupType ?? 'listbox'); - - const popup = new ComboboxPopupPattern({ - popupType, - controlTarget: signal(controlTarget), - activeDescendant, - popupId, - }); - - const pattern = new ComboboxPattern({ - alwaysExpanded, - value, - element: signal(element), - popup: signal(popup), - inlineSuggestion, - disabled, - readonly, - expanded, - expandable: signal(true), - }); + const {combobox, inputEl, containerEl, firstMatch, inputValue} = getComboboxPattern(inputs); + const {listbox, options} = getListboxPattern(combobox, [ + 'Apple', + 'Apricot', + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + 'Cherry', + 'Clementine', + 'Cranberry', + ]); + + (combobox.inputs.popupControls as WritableSignalLike).set(listbox); return { - pattern, - element, - value, - expanded, - alwaysExpanded, - inlineSuggestion, - disabled, - readonly, - popup, - controlTarget, + combobox, + listbox, + options, + inputEl: inputEl()!, + containerEl: containerEl()!, + firstMatch, + inputValue, }; } - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - describe('Aria-autocomplete calculation', () => { - it('should return "list" when only popup is present', () => { - const {pattern} = setup(); - expect(pattern.autocomplete()).toBe('list'); + describe('Navigation', () => { + it('should navigate to the first item on ArrowDown', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(down()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); }); - it('should return "both" when popup and inline suggestion are present', () => { - const {pattern} = setup({inlineSuggestion: 'suggestion'}); - expect(pattern.autocomplete()).toBe('both'); + it('should navigate to the last item on ArrowUp', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(up()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[8]); }); - it('should return "none" when only dialog popup is present', () => { - const {pattern} = setup({popupType: 'dialog'}); - expect(pattern.autocomplete()).toBe('none'); + it('should navigate to the next item on ArrowDown when open', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[1]); }); - it('should return "inline" when dialog popup and inline suggestion are present', () => { - const {pattern} = setup({popupType: 'dialog', inlineSuggestion: 'suggestion'}); - expect(pattern.autocomplete()).toBe('inline'); + it('should navigate to the previous item on ArrowUp when open', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(up()); + combobox.onKeydown(up()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[7]); }); - }); - describe('Expansion via Keyboard', () => { - it('should open on ArrowDown when collapsed', () => { - const {pattern, expanded} = setup(); - expect(expanded()).toBe(false); - - pattern.onKeydown(createKeyboardEvent('keydown', 40, 'ArrowDown')); - expect(expanded()).toBe(true); + it('should navigate to the first item on Home when open', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(up()); + combobox.onKeydown(home()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); }); - it('should close on Escape when expanded', () => { - const {pattern, expanded} = setup(); - expanded.set(true); - - pattern.onKeydown(createKeyboardEvent('keydown', 27, 'Escape')); - expect(expanded()).toBe(false); + it('should navigate to the last item on End when open', () => { + const {combobox, listbox} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(end()); + expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[8]); }); }); - describe('Input handling', () => { - it('should update value and expand on input', () => { - const {pattern, element, value, expanded} = setup(); - expect(expanded()).toBe(false); - - element.value = 'hello'; - pattern.onInput({target: element} as unknown as Event); - - expect(value()).toBe('hello'); - expect(expanded()).toBe(true); + describe('Expansion', () => { + it('should open on ArrowDown', () => { + const {combobox} = getPatterns(); + expect(combobox.expanded()).toBe(false); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); }); - }); - describe('Focus handling', () => { - it('should track focus state', () => { - const {pattern} = setup(); - - pattern.onFocusin(); - expect(pattern.isFocused()).toBe(true); - - pattern.onFocusout(); - expect(pattern.isFocused()).toBe(false); + it('should open on ArrowUp', () => { + const {combobox} = getPatterns(); + expect(combobox.expanded()).toBe(false); + combobox.onKeydown(up()); + expect(combobox.expanded()).toBe(true); }); - }); - - describe('Inline Suggestion / Highlighting', () => { - it('should insert the inline suggestion into the input and select the remaining text', () => { - const {pattern, element, value, expanded, inlineSuggestion} = setup(); - value.set('App'); - inlineSuggestion.set('Apple'); - expanded.set(true); - pattern.isFocused.set(true); + it('should close on Escape', () => { + const {combobox} = getPatterns(); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(escape()); + expect(combobox.expanded()).toBe(false); + }); - pattern.highlightEffect(); + it('should close on Enter', () => { + const {combobox} = getPatterns(); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(enter()); + expect(combobox.expanded()).toBe(false); + }); - expect(element.value).toBe('Apple'); - expect(element.selectionStart).toBe(3); - expect(element.selectionEnd).toBe(5); + it('should not close on Enter if the option is disabled', () => { + const {combobox, options} = getPatterns(); + options()[0].disabled.set(true); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(enter()); + expect(combobox.expanded()).toBe(true); }); - it('should not highlight when deleting text', () => { - const {pattern, element, value, expanded, inlineSuggestion} = setup(); + it('should close on focusout', () => { + const {combobox} = getPatterns(); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(combobox.expanded()).toBe(false); + }); - value.set('App'); - inlineSuggestion.set('Apple'); - expanded.set(true); - pattern.isFocused.set(true); + it('should not close on focusout if focus moves to an element inside the container', () => { + const {combobox, containerEl} = getPatterns(); + const internalElement = document.createElement('div'); + containerEl.appendChild(internalElement); + combobox.onKeydown(down()); - const deleteEvent = new InputEvent('input', {inputType: 'deleteContentBackward'}); - Object.defineProperty(deleteEvent, 'target', {value: element}); - pattern.onInput(deleteEvent as Event); + expect(combobox.expanded()).toBe(true); - expect(pattern.isDeleting()).toBe(true); + const event = new FocusEvent('focusout', {relatedTarget: internalElement}); + combobox.onFocusOut(event); - pattern.highlightEffect(); + expect(combobox.expanded()).toBe(true); + }); - expect(element.value).not.toBe('Apple'); + it('should not expand when disabled', () => { + const {combobox, inputEl} = getPatterns({disabled: true}); + expect(combobox.expanded()).toBe(false); + combobox.onClick(clickInput(inputEl)); + expect(combobox.expanded()).toBe(false); }); }); - describe('Select-only combobox behavior', () => { - function setupSelectOnly() { - const selectOnlyElement = document.createElement('div'); - const {pattern, expanded, controlTarget} = setup(); - - // Override element to be select-only - pattern.inputs.element = signal(selectOnlyElement); + describe('Selection', () => { + let combobox: ComboboxPattern; + let listbox: ComboboxListboxPattern; + let inputEl: HTMLInputElement; + let options: () => TestOption[]; + let firstMatch: WritableSignalLike; - return {pattern, expanded, selectOnlyElement, controlTarget}; + function type(text: string, opts: {backspace?: boolean} = {}) { + _type(text, inputEl, combobox, options(), listbox, firstMatch, opts.backspace); } - it('should toggle expansion on click', () => { - const {pattern, expanded} = setupSelectOnly(); - expect(expanded()).toBe(false); + describe('when filterMode is "manual"', () => { + beforeEach(() => { + ({combobox, listbox, inputEl, options, firstMatch} = getPatterns({ + filterMode: 'manual', + })); + }); + + it('should select and commit on click', () => { + combobox.onClick(clickOption(listbox.inputs.items(), 0)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); + expect(inputEl.value).toBe('Apple'); + }); + + it('should select and commit to input on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); + expect(inputEl.value).toBe('Apple'); + }); + + it('should select on focusout if the input text exactly matches an item', () => { + type('Apple'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); + }); + + it('should deselect on close if the input text does not match any options', () => { + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + + expect(listbox.inputs.values()).toEqual(['Apple']); + type('Appl', {backspace: true}); + expect(listbox.inputs.values()).toEqual(['Apple']); + combobox.onKeydown(escape()); + expect(listbox.inputs.values()).toEqual([]); + }); + + it('should not select on navigation', () => { + combobox.onKeydown(down()); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); + }); + + it('should not select on input', () => { + type('A'); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); + }); + + it('should not select on focusout if the input text does not match an item', () => { + type('Appl'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); + expect(inputEl.value).toBe('Appl'); + }); + }); - pattern.onClick(new PointerEvent('click')); - expect(expanded()).toBe(true); + describe('when filterMode is "auto-select"', () => { + beforeEach(() => { + ({combobox, listbox, inputEl, options, firstMatch} = getPatterns({ + filterMode: 'auto-select', + })); + }); + + it('should select and commit on click', () => { + combobox.onClick(clickOption(listbox.inputs.items(), 3)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[3]); + expect(listbox.inputs.values()).toEqual(['Blackberry']); + expect(inputEl.value).toBe('Blackberry'); + }); + + it('should select and commit on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + }); + + it('should select the first item on arrow down when collapsed', () => { + combobox.onKeydown(down()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); + }); + + it('should select the last item on arrow up when collapsed', () => { + combobox.onKeydown(up()); + expect(listbox.getSelectedItems()[0]).toBe( + listbox.inputs.items()[listbox.inputs.items().length - 1], + ); + expect(listbox.inputs.values()).toEqual(['Cranberry']); + }); + + it('should select on navigation', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[1]); + expect(listbox.inputs.values()).toEqual(['Apricot']); + }); + + it('should select the first option on input', () => { + type('A'); + expect(listbox.inputs.values()).toEqual(['Apple']); + + type('Apr'); + expect(listbox.inputs.values()).toEqual(['Apricot']); + }); + + it('should commit the selected option on focusout', () => { + combobox.onKeydown(down()); + type('App'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(inputEl.value).toBe('Apple'); + }); + + it('should not commit an option on focusout if the popup is closed', () => { + type('A'); + combobox.onKeydown(escape()); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(inputEl.value).toBe('A'); + }); + }); - pattern.onClick(new PointerEvent('click')); - expect(expanded()).toBe(false); + describe('when filterMode is "highlight"', () => { + beforeEach(() => { + ({combobox, listbox, inputEl, options, firstMatch} = getPatterns({ + filterMode: 'highlight', + })); + }); + + it('should select and commit on click', () => { + combobox.onClick(clickOption(listbox.inputs.items(), 3)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[3]); + expect(listbox.inputs.values()).toEqual(['Blackberry']); + expect(inputEl.value).toBe('Blackberry'); + }); + + it('should select and commit on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + }); + + it('should select the first item on arrow down when collapsed', () => { + combobox.onKeydown(down()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); + }); + + it('should select the last item on arrow up when collapsed', () => { + combobox.onKeydown(up()); + expect(listbox.getSelectedItems()[0]).toBe( + listbox.inputs.items()[listbox.inputs.items().length - 1], + ); + expect(listbox.inputs.values()).toEqual(['Cranberry']); + }); + + it('should select on navigation', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[1]); + expect(listbox.inputs.values()).toEqual(['Apricot']); + }); + + it('should select the first option on input', () => { + type('A'); + expect(listbox.inputs.values()).toEqual(['Apple']); + + type('Apr'); + expect(listbox.inputs.values()).toEqual(['Apricot']); + }); + + it('should commit the selected option on navigation', () => { + combobox.onKeydown(down()); + expect(inputEl.value).toBe('Apple'); + combobox.onKeydown(down()); + expect(inputEl.value).toBe('Apricot'); + }); + + it('should commit the selected option on focusout', () => { + combobox.onKeydown(down()); + type('App'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(inputEl.value).toBe('Apple'); + }); + + it('should insert a highlighted completion string on input', () => { + type('A'); + expect(inputEl.value).toBe('Apple'); + expect(inputEl.selectionStart).toBe(1); + expect(inputEl.selectionEnd).toBe(5); + }); + + it('should should remember which option was highlighted after navigating', () => { + type('A'); + combobox.onKeydown(down()); + + expect(inputEl.value).toBe('Apricot'); + expect(inputEl.selectionStart).toBe(7); + expect(inputEl.selectionEnd).toBe(7); + + combobox.onKeydown(up()); + + expect(inputEl.value).toBe('Apple'); + expect(inputEl.selectionStart).toBe(1); + expect(inputEl.selectionEnd).toBe(5); + }); }); + }); - it('should open on Enter or Space when collapsed', () => { - const {pattern, expanded} = setupSelectOnly(); + describe('Readonly mode', () => { + describe('with single-select', () => { + it('should select and close on selection', () => { + const {combobox, listbox, inputEl} = getPatterns({readonly: true}); + combobox.onClick(clickOption(listbox.inputs.items(), 2)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + expect(combobox.expanded()).toBe(false); + }); + + it('should close on escape', () => { + const {combobox} = getPatterns({readonly: true}); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(escape()); + expect(combobox.expanded()).toBe(false); + }); + }); - pattern.onKeydown(createKeyboardEvent('keydown', 13, 'Enter')); - expect(expanded()).toBe(true); + describe('with multi-select', () => { + it('should allow users to select multiple options', () => { + const {combobox, listbox, inputEl} = getPatterns({readonly: true}); + (listbox.inputs.multi as WritableSignalLike).set(true); - expanded.set(false); + combobox.onClick(clickOption(listbox.inputs.items(), 1)); + combobox.onClick(clickOption(listbox.inputs.items(), 2)); - pattern.onKeydown(createKeyboardEvent('keydown', 32, ' ')); - expect(expanded()).toBe(true); + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana']); + expect(inputEl.value).toBe('Apricot, Banana'); + }); }); }); +}); - describe('alwaysExpanded behavior', () => { - it('should stay open on Escape when alwaysExpanded is true', () => { - const {pattern, expanded} = setup({alwaysExpanded: true}); - expanded.set(true); +describe('Combobox with Tree Pattern', () => { + function getPatterns( + inputs: Partial<{ + [K in keyof TestInputs]: TestInputs[K] extends WritableSignalLike ? T : never; + }> = {}, + ) { + const {combobox, inputEl, containerEl, firstMatch, inputValue} = getComboboxPattern(inputs); + const {tree, items} = getTreePattern(combobox, [ + {value: 'Fruit', children: [{value: 'Apple'}, {value: 'Banana'}, {value: 'Cantaloupe'}]}, + {value: 'Vegetables', children: [{value: 'Broccoli'}, {value: 'Carrot'}, {value: 'Lettuce'}]}, + {value: 'Grains', children: [{value: 'Rice'}, {value: 'Wheat'}]}, + ]); + + (combobox.inputs.popupControls as WritableSignalLike).set(tree); + + return { + combobox, + tree, + items: items, + inputEl: inputEl()!, + containerEl: containerEl()!, + firstMatch, + inputValue, + }; + } - pattern.onKeydown(createKeyboardEvent('keydown', 27, 'Escape')); - expect(expanded()).toBe(true); + describe('Navigation', () => { + it('should navigate to the first focusable item on ArrowDown', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Fruit'); }); - }); - describe('Blur behavior', () => { - it('should close when focus leaves both combobox and popup', async () => { - const {pattern, expanded} = setup(); - expanded.set(true); - pattern.isFocused.set(false); - pattern.inputs.popup()!.isFocused.set(false); + it('should navigate to the last focusable item on ArrowUp', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(up()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Grains'); + }); - pattern.onFocusout(); - await wait(100); + it('should navigate to the next focusable item on ArrowDown when open', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Vegetables'); + }); - expect(expanded()).toBe(false); + it('should navigate to the previous item on ArrowUp when open', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(up()); + combobox.onKeydown(up()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Vegetables'); }); - it('should remain open if popup is focused', async () => { - const {pattern, expanded} = setup(); - expanded.set(true); - pattern.isFocused.set(false); - pattern.inputs.popup()!.isFocused.set(true); + it('should expand a closed node on ArrowRight', () => { + const {combobox, tree} = getPatterns(); + const before = tree.inputs + .items() + .filter(i => i.visible()) + .map(i => i.searchTerm()); + expect(before).toEqual(['Fruit', 'Vegetables', 'Grains']); + combobox.onKeydown(down()); + combobox.onKeydown(right()); + const after = tree.inputs + .items() + .filter(i => i.visible()) + .map(i => i.searchTerm()); + expect(after).toEqual(['Fruit', 'Apple', 'Banana', 'Cantaloupe', 'Vegetables', 'Grains']); + }); - pattern.onFocusout(); - await wait(100); + it('should navigate to the next item on ArrowRight when already expanded', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onKeydown(right()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Apple'); + }); - expect(expanded()).toBe(true); + it('should collapse an open node on ArrowLeft', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onKeydown(left()); + const after = tree.inputs + .items() + .filter(i => i.visible()) + .map(i => i.searchTerm()); + expect(after).toEqual(['Fruit', 'Vegetables', 'Grains']); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Fruit'); }); - }); - describe('Advanced Combo Keys Relay', () => { - it('should forward Shift + ArrowUp/ArrowDown for editable inputs', () => { - const {pattern, expanded} = setup(); - expanded.set(true); + it('should navigate to the parent node on ArrowLeft when in a child node', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onKeydown(right()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Apple'); + combobox.onKeydown(left()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Fruit'); + }); - const shiftUp = createKeyboardEvent('keydown', 38, 'ArrowUp'); - Object.defineProperty(shiftUp, 'shiftKey', {value: true}); - pattern.onKeydown(shiftUp); - expect(pattern.keyboardEventRelay()).toBe(shiftUp); + it('should navigate to the first focusable item on Home when open', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(up()); + combobox.onKeydown(home()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Fruit'); + }); - const shiftDown = createKeyboardEvent('keydown', 40, 'ArrowDown'); - Object.defineProperty(shiftDown, 'shiftKey', {value: true}); - pattern.onKeydown(shiftDown); - expect(pattern.keyboardEventRelay()).toBe(shiftDown); + it('should navigate to the last focusable item on End when open', () => { + const {combobox, tree} = getPatterns(); + combobox.onKeydown(down()); + combobox.onKeydown(end()); + expect(tree.inputs.activeItem()?.searchTerm()).toBe('Grains'); }); + }); - it('should NOT forward Ctrl+A or Shift+Home/End for editable inputs', () => { - const {pattern, expanded} = setup(); - expanded.set(true); + describe('Selection', () => { + let combobox: ComboboxPattern; + let tree: ComboboxTreePattern; + let inputEl: HTMLInputElement; + let items: () => TreeItemPattern[]; + let firstMatch: WritableSignalLike; - const ctrlA = createKeyboardEvent('keydown', 65, 'a'); - Object.defineProperty(ctrlA, 'ctrlKey', {value: true}); - pattern.onKeydown(ctrlA); - expect(pattern.keyboardEventRelay()).toBeUndefined(); + function type(text: string, opts: {backspace?: boolean} = {}) { + _type(text, inputEl, combobox, items(), tree, firstMatch, opts.backspace); + } - const shiftHome = createKeyboardEvent('keydown', 36, 'Home'); - Object.defineProperty(shiftHome, 'shiftKey', {value: true}); - pattern.onKeydown(shiftHome); - expect(pattern.keyboardEventRelay()).toBeUndefined(); + describe('when filterMode is "manual"', () => { + beforeEach(() => { + ({combobox, tree, inputEl, items, firstMatch} = getPatterns({ + filterMode: 'manual', + })); + }); + + it('should select and commit on click', () => { + combobox.onClick(clickTreeItem(tree.inputs.items(), 0)); + expect(tree.inputs.values()).toEqual(['Fruit']); + expect(inputEl.value).toBe('Fruit'); + }); + + it('should select and commit to input on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.items()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); + expect(inputEl.value).toBe('Fruit'); + }); + + it('should select on focusout if the input text exactly matches an item', () => { + combobox.onClick(clickInput(inputEl)); + type('Apple'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(tree.inputs.values()).toEqual(['Apple']); + }); + + it('should deselect on close if the input text does not match any options', () => { + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + + expect(tree.inputs.values()).toEqual(['Fruit']); + type('Frui', {backspace: true}); + expect(tree.inputs.values()).toEqual(['Fruit']); + combobox.onKeydown(escape()); + expect(tree.inputs.values()).toEqual([]); + }); + + it('should not select on navigation', () => { + combobox.onKeydown(down()); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); + }); + + it('should not select on input', () => { + type('A'); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); + }); + + it('should not select on focusout if the input text does not match an item', () => { + type('Appl'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); + expect(inputEl.value).toBe('Appl'); + }); }); - it('should forward Ctrl+A and Shift+Home/End for select-only (non-editable) comboboxes', () => { - const selectOnlyElement = document.createElement('div'); - const {pattern, expanded} = setup(); - pattern.inputs.element = signal(selectOnlyElement); - expanded.set(true); - - const ctrlA = createKeyboardEvent('keydown', 65, 'a'); - Object.defineProperty(ctrlA, 'ctrlKey', {value: true}); - pattern.onKeydown(ctrlA); - expect(pattern.keyboardEventRelay()).toBe(ctrlA); + describe('when filterMode is "auto-select"', () => { + beforeEach(() => { + ({combobox, tree, inputEl, items, firstMatch} = getPatterns({ + filterMode: 'auto-select', + })); + }); + + it('should select and commit on click', () => { + // Expand Fruit: Down -> Right + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onClick(clickTreeItem(tree.inputs.items(), 2)); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.items()[2]); + expect(tree.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + }); + + it('should select and commit on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(tree.inputs.values()).toEqual(['Grains']); + expect(inputEl.value).toBe('Grains'); + }); + + it('should select the first item on arrow down when collapsed', () => { + combobox.onKeydown(down()); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.items()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); + }); + + it('should select the last focusable item on arrow up when collapsed', () => { + combobox.onKeydown(up()); + expect(tree.inputs.values()).toEqual(['Grains']); + }); + + it('should select on navigation', () => { + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onKeydown(right()); + expect(tree.inputs.values()).toEqual(['Apple']); + }); + + it('should select the first option on input', () => { + type('B'); + expect(tree.inputs.values()).toEqual(['Banana']); + + type('Bro'); + expect(tree.inputs.values()).toEqual(['Broccoli']); + }); + + it('should commit the selected option on focusout', () => { + combobox.onKeydown(down()); + type('App'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(inputEl.value).toBe('Apple'); + }); + }); - const shiftHome = createKeyboardEvent('keydown', 36, 'Home'); - Object.defineProperty(shiftHome, 'shiftKey', {value: true}); - pattern.onKeydown(shiftHome); - expect(pattern.keyboardEventRelay()).toBe(shiftHome); + describe('when filterMode is "highlight"', () => { + beforeEach(() => { + ({combobox, tree, inputEl, items, firstMatch} = getPatterns({ + filterMode: 'highlight', + })); + }); + + it('should select and commit on click', () => { + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onClick(clickTreeItem(tree.inputs.items(), 2)); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.items()[2]); + expect(tree.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + }); + + it('should select and commit on Enter', () => { + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(down()); + combobox.onKeydown(enter()); + expect(tree.inputs.values()).toEqual(['Grains']); + expect(inputEl.value).toBe('Grains'); + }); + + it('should select the first item on arrow down when collapsed', () => { + combobox.onKeydown(down()); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.items()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); + }); + + it('should select the last focusable item on arrow up when collapsed', () => { + combobox.onKeydown(up()); + expect(tree.inputs.values()).toEqual(['Grains']); + }); + + it('should select on navigation', () => { + combobox.onKeydown(down()); + combobox.onKeydown(right()); + combobox.onKeydown(right()); + expect(tree.inputs.values()).toEqual(['Apple']); + }); + + it('should select the first option on input', () => { + type('B'); + expect(tree.inputs.values()).toEqual(['Banana']); + + type('Bro'); + expect(tree.inputs.values()).toEqual(['Broccoli']); + }); + + it('should commit the selected option on navigation', () => { + combobox.onKeydown(down()); + expect(inputEl.value).toBe('Fruit'); + combobox.onKeydown(right()); + combobox.onKeydown(right()); + expect(inputEl.value).toBe('Apple'); + combobox.onKeydown(down()); + expect(tree.inputs.values()).toEqual(['Banana']); + }); + + it('should commit the selected option on focusout', () => { + type('App'); + combobox.onFocusOut(new FocusEvent('focusout')); + expect(inputEl.value).toBe('Apple'); + }); + + it('should insert a highlighted completion string on input', () => { + type('A'); + expect(inputEl.value).toBe('Apple'); + expect(inputEl.selectionStart).toBe(1); + expect(inputEl.selectionEnd).toBe(5); + }); }); }); - describe('Readonly', () => { - it('should ignore input when readonly', () => { - const {pattern, expanded, value} = setup({readonly: true}); - const inputEl = document.createElement('input'); - inputEl.value = 'abc'; - pattern.onInput({target: inputEl} as unknown as Event); + describe('Readonly mode', () => { + it('should select and close on selection', () => { + const {combobox, tree, inputEl} = getPatterns({readonly: true}); + combobox.onClick(clickInput(inputEl)); + expect(combobox.expanded()).toBe(true); + combobox.onClick(clickTreeItem(tree.inputs.items(), 0)); + expect(tree.inputs.values()).toEqual(['Fruit']); + expect(inputEl.value).toBe('Fruit'); + expect(combobox.expanded()).toBe(false); + }); - expect(expanded()).toBe(false); - expect(value()).toBe(''); + it('should close on escape', () => { + const {combobox} = getPatterns({readonly: true}); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(escape()); + expect(combobox.expanded()).toBe(false); }); }); }); diff --git a/src/aria/private/combobox/combobox.ts b/src/aria/private/combobox/combobox.ts index 444b44127455..fd93cc1ccc4a 100644 --- a/src/aria/private/combobox/combobox.ts +++ b/src/aria/private/combobox/combobox.ts @@ -6,194 +6,323 @@ * found in the LICENSE file at https://angular.dev/license */ -import {KeyboardEventManager, ClickEventManager, Modifier} from '../behaviors/event-manager'; -import {computed, signal, untracked} from '@angular/core'; -import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; -import {ExpansionItem} from '../behaviors/expansion/expansion'; - -/** Represents the required inputs for a simple combobox. */ -export interface ComboboxInputs extends ExpansionItem { - /** Whether the combobox should always remain expanded. */ - alwaysExpanded: SignalLike; +import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; +import { + computed, + signal, + SignalLike, + WritableSignalLike, +} from '../behaviors/signal-like/signal-like'; +import {ListItem} from '../behaviors/list/list'; + +/** Represents the required inputs for a combobox. */ +export interface ComboboxInputs, V> { + /** The controls for the popup associated with the combobox. */ + popupControls: SignalLike< + ComboboxListboxControls | ComboboxTreeControls | ComboboxDialogPattern | undefined + >; - /** The value of the combobox. */ - value: WritableSignalLike; + /** The HTML input element that serves as the combobox input. */ + inputEl: SignalLike; - /** The element that the combobox is attached to. */ - element: SignalLike; + /** The HTML element that serves as the combobox container. */ + containerEl: SignalLike; - /** The popup associated with the combobox. */ - popup: SignalLike; + /** The filtering mode for the combobox. */ + filterMode: SignalLike<'manual' | 'auto-select' | 'highlight'>; - /** An inline suggestion to be displayed in the input. */ - inlineSuggestion: SignalLike; + /** The current value of the combobox. */ + inputValue?: WritableSignalLike; + + /** The value of the first matching item in the popup. */ + firstMatch: SignalLike; /** Whether the combobox is disabled. */ disabled: SignalLike; - /** Whether the combobox is readonly. */ + /** Whether the combobox is read-only. */ readonly: SignalLike; - /** Whether the combobox is soft disabled. */ - softDisabled?: SignalLike; + /** Whether the combobox is in a right-to-left context. */ + textDirection: SignalLike<'rtl' | 'ltr'>; + + /** Whether the combobox is always expanded. */ + alwaysExpanded: SignalLike; } -/** Controls the state of a simple combobox. */ -export class ComboboxPattern { - /** The expanded state of the combobox. */ - readonly isExpanded = computed(() => this.inputs.alwaysExpanded() || this.inputs.expanded()); +/** An interface that allows combobox popups to expose the necessary controls for the combobox. */ +export interface ComboboxListboxControls, V> { + /** A unique identifier for the popup. */ + id: () => string; - /** The value of the combobox. */ - readonly value: WritableSignalLike; + /** The ARIA role for the popup. */ + role: SignalLike<'listbox' | 'tree' | 'grid'>; - /** The element that the combobox is attached to. */ - readonly element = () => this.inputs.element(); + // TODO(wagnermaciel): Add validation that ensures only readonly comboboxes can have multi-select popups. - /** Whether the combobox is disabled. */ - readonly disabled = () => this.inputs.disabled(); + /** Whether multiple items in the popup can be selected at once. */ + multi: SignalLike; - /** Whether the combobox is readonly. */ - readonly readonly = () => this.inputs.readonly(); + /** The ID of the active item in the popup. */ + activeId: SignalLike; - /** Whether the combobox is soft disabled. */ - readonly softDisabled = () => this.inputs.softDisabled?.() ?? true; + /** The list of items in the popup. */ + items: SignalLike; - /** An inline suggestion to be displayed in the input. */ - readonly inlineSuggestion = () => this.inputs.inlineSuggestion(); + /** Navigates to the given item in the popup. */ + focus: (item: T, opts?: {focusElement?: boolean}) => void; - /** The ID of the active descendant in the popup. */ - readonly activeDescendant = computed(() => this.inputs.popup()?.activeDescendant()); + /** Navigates to the next item in the popup. */ + next: () => void; - /** The ID of the popup. */ - readonly popupId = computed(() => this.inputs.popup()?.popupId()); + /** Navigates to the previous item in the popup. */ + prev: () => void; - /** The type of the popup. */ - readonly popupType = computed(() => this.inputs.popup()?.popupType()); + /** Navigates to the first item in the popup. */ + first: () => void; - /** The autocomplete behavior of the combobox. */ - readonly autocomplete = computed<'none' | 'inline' | 'list' | 'both'>(() => { - const popupType = this.popupType(); - const hasAutocompletePopup = !!this.inputs.popup() && popupType !== 'dialog'; - const hasInlineSuggestion = !!this.inlineSuggestion(); - if (hasAutocompletePopup && hasInlineSuggestion) { - return 'both'; - } - if (hasAutocompletePopup) { - return 'list'; - } - if (hasInlineSuggestion) { - return 'inline'; + /** Navigates to the last item in the popup. */ + last: () => void; + + /** Selects the current item in the popup. */ + select: (item?: T) => void; + + /** Toggles the selection state of the given item in the popup. */ + toggle: (item?: T) => void; + + /** Clears the selection state of the popup. */ + clearSelection: () => void; + + /** Removes focus from any item in the popup. */ + unfocus: () => void; + + /** Returns the item corresponding to the given event. */ + getItem: (e: PointerEvent) => T | undefined; + + /** Returns the currently active (focused) item in the popup. */ + getActiveItem: () => T | undefined; + + /** Returns the currently selected items in the popup. */ + getSelectedItems: () => T[]; + + /** Sets the value of the combobox based on the selected item. */ + setValue: (value: V | undefined) => void; // For re-setting the value if the popup was destroyed. +} + +export interface ComboboxTreeControls, V> extends ComboboxListboxControls< + T, + V +> { + /** Whether the currently active item in the popup is collapsible. */ + isItemCollapsible: () => boolean; + + /** Expands the currently active item in the popup. */ + expandItem: () => void; + + /** Collapses the currently active item in the popup. */ + collapseItem: () => void; + + /** Checks if the currently active item in the popup is expandable. */ + isItemExpandable: (item?: T) => boolean; + + /** Expands all nodes in the tree. */ + expandAll: () => void; + + /** Collapses all nodes in the tree. */ + collapseAll: () => void; + + /** Toggles the expansion state of the currently active item in the popup. */ + toggleExpansion: (item?: T) => void; + + /** Whether the current active item is selectable. */ + isItemSelectable: (item?: T) => boolean; +} + +/** Controls the state of a combobox. */ +export class ComboboxPattern, V> { + /** Whether the combobox is expanded. */ + expanded = signal(false); + + /** Whether the combobox is disabled. */ + disabled = () => this.inputs.disabled(); + + /** The ID of the active item in the combobox. */ + activeDescendant = computed(() => { + const popupControls = this.inputs.popupControls(); + if (popupControls instanceof ComboboxDialogPattern) { + return null; } - return 'none'; + + return popupControls?.activeId() ?? null; }); - /** A relay for keyboard events to the popup. */ - readonly keyboardEventRelay = signal(undefined); + /** The currently highlighted item in the combobox. */ + highlightedItem = signal(undefined); + + /** Whether the most recent input event was a deletion. */ + isDeleting = false; /** Whether the combobox is focused. */ - readonly isFocused = signal(false); + isFocused = signal(false); - /** Whether the most recent input event was a deletion. */ - readonly isDeleting = signal(false); + /** Whether the combobox has ever been focused. */ + hasBeenFocused = signal(false); + + /** The key used to navigate to the previous item in the list. */ + expandKey = computed(() => (this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight')); - /** Whether the combobox is editable (i.e., an input or textarea). */ - readonly isEditable = computed( - () => - this.element().tagName.toLowerCase() === 'input' || - this.element().tagName.toLowerCase() === 'textarea', + /** The key used to navigate to the next item in the list. */ + collapseKey = computed(() => + this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft', ); - /** The aria-readonly attribute value for non-editable comboboxes. */ - readonly ariaReadonly = computed(() => (this.readonly() && !this.isEditable() ? 'true' : null)); + /** The ID of the popup associated with the combobox. */ + popupId = computed(() => this.inputs.popupControls()?.id() || null); - /** The native readonly attribute value for editable comboboxes. */ - readonly nativeReadonly = computed(() => - (this.readonly() || this.disabled()) && this.isEditable() ? '' : null, - ); + /** The autocomplete behavior of the combobox. */ + autocomplete = computed(() => (this.inputs.filterMode() === 'highlight' ? 'both' : 'list')); - /** The native disabled attribute value for hard-disabled comboboxes. */ - readonly nativeDisabled = computed(() => (this.disabled() && !this.softDisabled() ? '' : null)); + /** The ARIA role of the popup associated with the combobox. */ + hasPopup = computed(() => this.inputs.popupControls()?.role() || null); + + /** Whether the combobox is read-only. */ + readonly = computed(() => this.inputs.readonly() || this.inputs.disabled() || null); + + /** Returns the listbox controls for the combobox. */ + listControls = () => { + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return null; + } + + return popupControls; + }; + + /** Returns the tree controls for the combobox. */ + treeControls = () => { + const popupControls = this.inputs.popupControls(); + + if (popupControls?.role() === 'tree') { + return popupControls as ComboboxTreeControls; + } + + return null; + }; /** The keydown event manager for the combobox. */ - // TODO(tjshiu): Allow combo keys in combobox (#33101). keydown = computed(() => { const manager = new KeyboardEventManager(); + const popupControls = this.inputs.popupControls(); - if (!this.isExpanded()) { - if (!this.readonly()) { - manager.on('ArrowDown', () => this.inputs.expanded.set(true)); + if (!popupControls) { + return manager; + } + + if (popupControls instanceof ComboboxDialogPattern) { + if (!this.expanded()) { + manager.on('ArrowUp', () => this.open()).on('ArrowDown', () => this.open()); - if (!this.isEditable()) { - manager.on('Enter', () => this.inputs.expanded.set(true)); - manager.on(' ', () => this.inputs.expanded.set(true)); + if (this.readonly()) { + manager.on('Enter', () => this.open()).on(' ', () => this.open()); } } return manager; } + if (!this.inputs.alwaysExpanded()) { + manager.on('Escape', () => this.close({reset: !this.readonly()})); + } + + if (!this.expanded()) { + manager + .on('ArrowDown', () => this.open({first: true})) + .on('ArrowUp', () => this.open({last: true})); + + if (this.readonly()) { + manager + .on('Enter', () => this.open({selected: true})) + .on(' ', () => this.open({selected: true})); + } + + return manager; + } + manager - .on( - 'ArrowLeft', - e => { - this.keyboardEventRelay.set(e); - }, - {preventDefault: this.popupType() !== 'listbox', ignoreRepeat: false}, - ) - .on( - 'ArrowRight', - e => { - this.keyboardEventRelay.set(e); - }, - {preventDefault: this.popupType() !== 'listbox', ignoreRepeat: false}, - ) - .on('ArrowUp', e => this.keyboardEventRelay.set(e), {ignoreRepeat: false}) - .on('ArrowDown', e => this.keyboardEventRelay.set(e), {ignoreRepeat: false}) - .on(Modifier.Shift, 'ArrowUp', e => this.keyboardEventRelay.set(e), {ignoreRepeat: false}) - .on(Modifier.Shift, 'ArrowDown', e => this.keyboardEventRelay.set(e), {ignoreRepeat: false}) - .on('Home', e => this.keyboardEventRelay.set(e)) - .on('End', e => this.keyboardEventRelay.set(e)) - .on('PageUp', e => this.keyboardEventRelay.set(e)) - .on('PageDown', e => this.keyboardEventRelay.set(e)) - .on('Escape', () => { - if (!this.inputs.alwaysExpanded()) { - this.inputs.expanded.set(false); - } + .on('ArrowDown', () => this.next(), {ignoreRepeat: false}) + .on('ArrowUp', () => this.prev(), {ignoreRepeat: false}) + .on('Home', () => this.first()) + .on('End', () => this.last()); + + if (this.readonly()) { + manager.on(' ', () => this.select({commit: true, close: !popupControls.multi()})); + } + + if (popupControls.role() === 'listbox') { + manager.on('Enter', () => { + this.select({commit: true, close: !popupControls.multi()}); }); + } - if (!this.readonly()) { - manager.on('Enter', e => this.keyboardEventRelay.set(e)); + const treeControls = this.treeControls(); + + if (treeControls?.isItemSelectable()) { + manager.on('Enter', () => this.select({commit: true, close: true})); } - if (!this.isEditable() && !this.readonly()) { + if (treeControls?.isItemExpandable()) { manager - .on(' ', e => this.keyboardEventRelay.set(e)) - .on([Modifier.Ctrl, Modifier.Meta], 'a', e => this.keyboardEventRelay.set(e)) - .on([Modifier.Ctrl, Modifier.Meta], 'A', e => this.keyboardEventRelay.set(e)) - .on(Modifier.Shift, 'Home', e => this.keyboardEventRelay.set(e)) - .on(Modifier.Shift, 'End', e => this.keyboardEventRelay.set(e)) - .on(/^.$/, e => { - this.keyboardEventRelay.set(e); - }); + .on(this.expandKey(), () => this.expandItem()) + .on(this.collapseKey(), () => this.collapseItem()); + + if (!treeControls.isItemSelectable()) { + manager.on('Enter', () => this.expandItem()); + } + } + + if (treeControls?.isItemCollapsible()) { + manager.on(this.collapseKey(), () => this.collapseItem()); } return manager; }); /** The click event manager for the combobox. */ - click = computed(() => { - const manager = new ClickEventManager(); + click = computed(() => + new PointerEventManager().on(e => { + if (e.target === this.inputs.inputEl()) { + if (this.readonly()) { + this.expanded() ? this.close() : this.open({selected: true}); + } + } - if (this.isEditable() || this.readonly()) return manager; + const controls = this.inputs.popupControls(); - manager.on(() => this.inputs.expanded.update(v => !v)); + if (controls instanceof ComboboxDialogPattern) { + return; + } - return manager; - }); + const item = controls?.getItem(e); - constructor(readonly inputs: ComboboxInputs) { - this.value = inputs.value; - } + if (item) { + if (controls?.role() === 'tree') { + const treeControls = controls as ComboboxTreeControls; + + if (treeControls.isItemExpandable(item) && !treeControls.isItemSelectable(item)) { + treeControls.toggleExpansion(item); + this.inputs.inputEl()?.focus(); + return; + } + } + + this.select({item, commit: true, close: !controls?.multi()}); + this.inputs.inputEl()?.focus(); // Return focus to the input after selecting. + } + }), + ); + + constructor(readonly inputs: ComboboxInputs) {} /** Handles keydown events for the combobox. */ onKeydown(event: KeyboardEvent) { @@ -203,124 +332,408 @@ export class ComboboxPattern { } /** Handles click events for the combobox. */ - onClick(event: PointerEvent) { - if (!this.disabled()) { - this.click().handle(event); + onClick(event: MouseEvent) { + if (!this.inputs.disabled()) { + this.click().handle(event as PointerEvent); + } + } + + /** Handles input events for the combobox. */ + onInput(event: Event) { + if (this.inputs.disabled() || this.inputs.readonly()) { + return; + } + + const inputEl = this.inputs.inputEl(); + + if (!inputEl) { + return; + } + + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + + this.open(); + this.inputs.inputValue?.set(inputEl.value); + this.isDeleting = event instanceof InputEvent && !!event.inputType.match(/^delete/); + + if (this.inputs.filterMode() === 'highlight' && !this.isDeleting) { + this.highlight(); } } /** Handles focus in events for the combobox. */ - onFocusin() { + onFocusIn() { + if (this.inputs.alwaysExpanded() && !this.hasBeenFocused()) { + const firstSelectedItem = this.listControls()?.getSelectedItems()[0]; + firstSelectedItem ? this.listControls()?.focus(firstSelectedItem) : this.first(); + } + this.isFocused.set(true); + this.hasBeenFocused.set(true); } /** Handles focus out events for the combobox. */ - onFocusout() { - // Give focus some time to move before we check it. - setTimeout(() => { - const comboboxFocused = this.isFocused(); - const popupFocused = !!this.inputs.popup()?.isFocused(); - - if (!this.inputs.alwaysExpanded() && !comboboxFocused && !popupFocused) { - this.inputs.expanded.set(false); + onFocusOut(event: FocusEvent) { + if (this.inputs.disabled()) { + return; + } + + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + + if ( + !(event.relatedTarget instanceof HTMLElement) || + !this.inputs.containerEl()?.contains(event.relatedTarget) + ) { + this.isFocused.set(false); + + if (!this.expanded()) { + return; + } + + if (this.readonly()) { + this.close(); + return; } - }); - this.isFocused.set(false); + if (this.inputs.filterMode() !== 'manual') { + this.commit(); + } else { + const item = popupControls + ?.items() + .find(i => i.searchTerm() === this.inputs.inputEl()?.value); + + if (item) { + this.select({item}); + } + } + + this.close(); + } } - /** Handles input events for the combobox. */ - onInput(event: Event) { - if (!(event.target instanceof HTMLInputElement)) return; - if (this.disabled() || this.readonly()) return; + /** The first matching item in the combobox. */ + firstMatch = computed(() => { + // TODO(wagnermaciel): Consider whether we should not provide this default behavior for the + // listbox. Instead, we may want to allow users to have no match so that typing does not focus + // any option. + if (this.listControls()?.role() === 'listbox') { + return this.listControls()?.items()[0]; + } + + return this.listControls() + ?.items() + .find(i => i.value() === this.inputs.firstMatch()); + }); - this.inputs.expanded.set(true); - this.value.set(event.target.value); - this.isDeleting.set(event instanceof InputEvent && !!event.inputType.match(/^delete/)); + /** Handles filtering logic for the combobox. */ + onFilter() { + if (this.readonly()) { + return; + } + + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + + // TODO(wagnermaciel) + // When the user first interacts with the combobox, the popup will lazily render for the first + // time. This is a simple way to detect this and avoid auto-focus & selection logic, but this + // should probably be moved to the component layer instead. + const isInitialRender = !this.inputs.inputValue?.().length && !this.isDeleting; + + if (isInitialRender) { + return; + } + + // Avoid refocusing the input if a filter event occurs after focus has left the combobox. + if (!this.isFocused()) { + return; + } + + if (this.inputs.popupControls()?.role() === 'tree') { + const treeControls = this.inputs.popupControls() as ComboboxTreeControls; + this.inputs.inputValue?.().length ? treeControls.expandAll() : treeControls.collapseAll(); + } + + const item = this.firstMatch(); + + if (!item) { + popupControls?.clearSelection(); + popupControls?.unfocus(); + return; + } + + popupControls?.focus(item); + + if (this.inputs.filterMode() !== 'manual') { + this.select({item}); + } + + if (this.inputs.filterMode() === 'highlight' && !this.isDeleting) { + this.highlight(); + } } /** Highlights the currently selected item in the combobox. */ - highlightEffect() { - const value = this.value(); - const inlineSuggestion = this.inlineSuggestion(); + highlight() { + const inputEl = this.inputs.inputEl(); + const selectedItems = this.listControls()?.getSelectedItems(); + const item = selectedItems?.[0]; - const isDeleting = untracked(() => this.isDeleting()); - const isFocused = untracked(() => this.isFocused()); - const isExpanded = this.isExpanded(); - - if (!inlineSuggestion || !isFocused || !isExpanded || isDeleting || this.readonly()) return; + if (!inputEl || !item) { + return; + } - const inputEl = this.element() as HTMLInputElement; - const isHighlightable = inlineSuggestion.toLowerCase().startsWith(value.toLowerCase()); + const isHighlightable = item + .searchTerm() + .toLowerCase() + .startsWith(this.inputs.inputValue!().toLowerCase()); if (isHighlightable) { - inputEl.value = value + inlineSuggestion.slice(value.length); - inputEl.setSelectionRange(value.length, inlineSuggestion.length); + inputEl.value = + this.inputs.inputValue!() + item.searchTerm().slice(this.inputs.inputValue!().length); + inputEl.setSelectionRange(this.inputs.inputValue!().length, item.searchTerm().length); + this.highlightedItem.set(item); } } - /** Relays keyboard events to the popup. */ - keyboardEventRelayEffect() { - const event = this.keyboardEventRelay(); - if (event === undefined) return; + /** Closes the combobox. */ + close(opts?: {reset: boolean}) { + const popupControls = this.inputs.popupControls(); + + if (this.inputs.alwaysExpanded()) { + return; + } + + if (popupControls instanceof ComboboxDialogPattern) { + this.expanded.set(false); + return; + } + + if (this.readonly()) { + this.expanded.set(false); + popupControls?.unfocus(); + return; + } + + if (!opts?.reset) { + if (this.inputs.filterMode() === 'manual') { + if ( + !this.listControls() + ?.items() + .some(i => i.searchTerm() === this.inputs.inputEl()?.value) + ) { + this.listControls()?.clearSelection(); + } + } + + this.expanded.set(false); + popupControls?.unfocus(); + return; + } + + if (!this.expanded()) { + this.inputs.inputValue?.set(''); + popupControls?.clearSelection(); - // Reset isDeleting when the user navigates, so that the highlight effect can run again. - this.isDeleting.set(false); + const inputEl = this.inputs.inputEl(); - untracked(() => { - const popup = this.inputs.popup(); - if (this.isExpanded()) { - const relayedEvent = new KeyboardEvent(event.type, event); - popup?.controlTarget()?.dispatchEvent(relayedEvent); + if (inputEl) { + inputEl.value = ''; } - }); + } else if (this.expanded()) { + this.expanded.set(false); + const selectedItem = popupControls?.getSelectedItems()?.[0]; + + if (selectedItem?.searchTerm() !== this.inputs.inputValue!()) { + popupControls?.clearSelection(); + } + + return; + } + + this.close(); + + if (!this.readonly()) { + popupControls?.clearSelection(); + } } -} -/** Represents the required inputs for a simple combobox popup. */ -export interface ComboboxPopupInputs { - /** The type of the popup. */ - popupType: SignalLike<'listbox' | 'tree' | 'grid' | 'dialog'>; + /** Opens the combobox. */ + open(nav?: {first?: boolean; last?: boolean; selected?: boolean}) { + this.expanded.set(true); + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } - /** The element that serves as the control target for the popup. */ - controlTarget: SignalLike; + const inputEl = this.inputs.inputEl(); - /** The ID of the active descendant in the popup. */ - activeDescendant: SignalLike; + if (inputEl && this.inputs.filterMode() === 'highlight') { + const isHighlighting = inputEl.selectionStart !== inputEl.value.length; + this.inputs.inputValue?.set(inputEl.value.slice(0, inputEl.selectionStart || 0)); + if (!isHighlighting) { + this.highlightedItem.set(undefined); + } + } - /** The ID of the popup. */ - popupId: SignalLike; -} + if (nav?.first) { + this.first(); + } + if (nav?.last) { + this.last(); + } + if (nav?.selected) { + const selectedItem = popupControls + ?.items() + .find(i => popupControls?.getSelectedItems().includes(i)); -/** Controls the state of a simple combobox popup. */ -export class ComboboxPopupPattern { - /** The type of the popup. */ - readonly popupType = () => this.inputs.popupType(); + if (selectedItem) { + popupControls?.focus(selectedItem); + } + } + } - /** The element that serves as the control target for the popup. */ - readonly controlTarget = () => this.inputs.controlTarget(); + /** Navigates to the next focusable item in the combobox popup. */ + next() { + this._navigate(() => this.listControls()?.next()); + } - /** The ID of the active descendant in the popup. */ - readonly activeDescendant = () => this.inputs.activeDescendant(); + /** Navigates to the previous focusable item in the combobox popup. */ + prev() { + this._navigate(() => this.listControls()?.prev()); + } - /** The ID of the popup. */ - readonly popupId = () => this.inputs.popupId(); + /** Navigates to the first focusable item in the combobox popup. */ + first() { + this._navigate(() => this.listControls()?.first()); + } - /** Whether the popup is focused. */ - readonly isFocused = signal(false); + /** Navigates to the last focusable item in the combobox popup. */ + last() { + this._navigate(() => this.listControls()?.last()); + } - constructor(readonly inputs: ComboboxPopupInputs) {} + /** Collapses the currently focused item in the combobox. */ + collapseItem() { + const controls = this.inputs.popupControls() as ComboboxTreeControls; + this._navigate(() => controls?.collapseItem()); + } - /** Handles focus in events for the popup. */ - onFocusin() { - this.isFocused.set(true); + /** Expands the currently focused item in the combobox. */ + expandItem() { + const controls = this.inputs.popupControls() as ComboboxTreeControls; + this._navigate(() => controls?.expandItem()); } - /** Handles focus out events for the popup. */ - onFocusout(event: FocusEvent) { - const focusTarget = event.relatedTarget as Element | null; - if (this.controlTarget()?.contains(focusTarget)) return; + /** Selects an item in the combobox popup. */ + select(opts: {item?: T; commit?: boolean; close?: boolean} = {}) { + const controls = this.listControls(); + + // When no item is specified (e.g. on keyboard toggle), get the active item instead. + // Note: this is only necessary for disabled check, as select/toggle will check active item too. + const item = opts.item ?? controls?.getActiveItem(); + + // Check if item is disabled before proceeding. + if (item?.disabled()) { + return; + } + + if (opts.item) { + controls?.focus(opts.item, {focusElement: false}); + } + + controls?.multi() ? controls.toggle(opts.item) : controls?.select(opts.item); - this.isFocused.set(false); + if (opts.commit) { + this.commit(); + } + if (opts.close) { + this.close(); + } + } + + /** Updates the value of the input based on the currently selected item. */ + commit() { + const inputEl = this.inputs.inputEl(); + const selectedItems = this.listControls()?.getSelectedItems(); + + if (!inputEl) { + return; + } + + inputEl.value = selectedItems?.map(i => i.searchTerm()).join(', ') || ''; + this.inputs.inputValue?.set(inputEl.value); + + if (this.inputs.filterMode() === 'highlight' && !this.readonly()) { + const length = inputEl.value.length; + inputEl.setSelectionRange(length, length); + } + } + + /** Navigates and handles additional actions based on filter mode. */ + private _navigate(operation: () => void) { + operation(); + + if (this.inputs.filterMode() !== 'manual') { + this.select(); + } + + if (this.inputs.filterMode() === 'highlight') { + // This is to handle when the user navigates back to the originally highlighted item. + // E.g. User types "Al", highlights "Alice", then navigates down and back up to "Alice". + const selectedItem = this.listControls()?.getSelectedItems()[0]; + + if (!selectedItem) { + return; + } + + if (selectedItem === this.highlightedItem()) { + this.highlight(); + } else { + const inputEl = this.inputs.inputEl()!; + inputEl.value = selectedItem?.searchTerm()!; + } + } + } +} + +export class ComboboxDialogPattern { + id = () => this.inputs.id(); + + role = () => 'dialog' as const; + + keydown = computed(() => { + return new KeyboardEventManager().on('Escape', () => this.inputs.combobox.close()); + }); + + constructor( + readonly inputs: { + combobox: ComboboxPattern; + element: SignalLike; + id: SignalLike; + }, + ) {} + + onKeydown(event: KeyboardEvent) { + this.keydown().handle(event); + } + + onClick(event: MouseEvent) { + // The "click" event fires on the dialog when the user clicks outside of the dialog content. + if (event.target === this.inputs.element()) { + this.inputs.combobox.close(); + } } } diff --git a/src/aria/private/deferred-content/deferred-content.spec.ts b/src/aria/private/deferred-content/deferred-content.spec.ts index 28da5f1cfcec..7ec79e023963 100644 --- a/src/aria/private/deferred-content/deferred-content.spec.ts +++ b/src/aria/private/deferred-content/deferred-content.spec.ts @@ -1,12 +1,4 @@ -import { - Component, - DebugElement, - Directive, - effect, - inject, - signal, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, DebugElement, Directive, effect, inject, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {DeferredContent, DeferredContentAware} from './deferred-content'; import {By} from '@angular/platform-browser'; @@ -26,7 +18,7 @@ describe('DeferredContent', () => { expect(collapsible.nativeElement.innerText).toBe(''); }); - it('creates the content when the container becomes visible.', async () => { + it('creates the content when visible.', async () => { collapsible.injector.get(Collapsible).contentVisible.set(true); await fixture.whenStable(); expect(collapsible.nativeElement.innerText).toBe('Lazy Content'); @@ -46,7 +38,7 @@ describe('DeferredContent', () => { expect(collapsible.nativeElement.innerText).toBe(''); }); - it('creates the content when first visible with preserveContent.', async () => { + it('creates the content when visible.', async () => { collapsible.injector.get(Collapsible).contentVisible.set(true); await fixture.whenStable(); expect(collapsible.nativeElement.innerText).toBe('Lazy Content'); @@ -91,7 +83,6 @@ class CollapsibleContent {}
`, imports: [Collapsible, CollapsibleContent], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestComponent { preserveContent = signal(false); diff --git a/src/aria/private/deferred-content/deferred-content.ts b/src/aria/private/deferred-content/deferred-content.ts index 180a51dab242..d6ee311fa768 100644 --- a/src/aria/private/deferred-content/deferred-content.ts +++ b/src/aria/private/deferred-content/deferred-content.ts @@ -53,19 +53,17 @@ export class DeferredContent implements OnDestroy { readonly deferredContentAware = signal(this._deferredContentAware); constructor() { - afterRenderEffect({ - write: () => { - if (this.deferredContentAware()?.contentVisible()) { - if (!this._isRendered) { - this._destroyContent(); - this._currentViewRef = this._viewContainerRef.createEmbeddedView(this._templateRef); - this._isRendered = true; - } - } else if (!this.deferredContentAware()?.preserveContent()) { + afterRenderEffect(() => { + if (this.deferredContentAware()?.contentVisible()) { + if (!this._isRendered) { this._destroyContent(); - this._isRendered = false; + this._currentViewRef = this._viewContainerRef.createEmbeddedView(this._templateRef); + this._isRendered = true; } - }, + } else if (!this.deferredContentAware()?.preserveContent()) { + this._destroyContent(); + this._isRendered = false; + } }); } diff --git a/src/aria/private/grid/BUILD.bazel b/src/aria/private/grid/BUILD.bazel index 8e86c4872d33..2dd61d5d75e5 100644 --- a/src/aria/private/grid/BUILD.bazel +++ b/src/aria/private/grid/BUILD.bazel @@ -17,7 +17,6 @@ ts_project( "//src/aria/private/behaviors/list-focus", "//src/aria/private/behaviors/list-navigation", "//src/aria/private/behaviors/signal-like", - "//src/aria/private/utils", ], ) diff --git a/src/aria/private/grid/cell.ts b/src/aria/private/grid/cell.ts index 121295985649..946d15def1e9 100644 --- a/src/aria/private/grid/cell.ts +++ b/src/aria/private/grid/cell.ts @@ -6,9 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ +import {KeyboardEventManager} from '../behaviors/event-manager'; +import {ListFocus} from '../behaviors/list-focus/list-focus'; +import {ListNavigation, ListNavigationInputs} from '../behaviors/list-navigation/list-navigation'; import { computed, signal, + linkedSignal, SignalLike, WritableSignalLike, } from '../behaviors/signal-like/signal-like'; @@ -18,15 +22,21 @@ import type {GridRowPattern} from './row'; import {GridCellWidgetPattern} from './widget'; /** The inputs for the `GridCellPattern`. */ -export interface GridCellInputs extends GridCell { +export interface GridCellInputs + extends + GridCell, + Omit< + ListNavigationInputs, + 'focusMode' | 'items' | 'activeItem' | 'softDisabled' | 'element' + > { /** The `GridPattern` that this cell belongs to. */ grid: SignalLike; /** The `GridRowPattern` that this cell belongs to. */ row: SignalLike; - /** The widget pattern contained within this cell, if any. */ - widget: SignalLike; + /** The widget patterns contained within this cell, if any. */ + widgets: SignalLike; /** The index of this cell's row within the grid. */ rowIndex: SignalLike; @@ -97,26 +107,139 @@ export class GridCellPattern implements GridCell { /** The tab index for the cell. If the cell contains a widget, the cell's tab index is -1. */ readonly tabIndex: SignalLike<-1 | 0> = computed(() => { - if (this.inputs.widget()) { + if (this.singleWidgetMode() || this.navigationActivated()) { return -1; } return this._tabIndex(); }); - /** The widget in the cell. */ - readonly widget: SignalLike = () => this.inputs.widget(); + // Single/Multi Widget Navigation Setup + + /** Whether the cell contains a single widget. */ + readonly singleWidgetMode: SignalLike = computed( + () => this.inputs.widgets().length === 1, + ); + + /** Whether the cell contains multiple widgets. */ + readonly multiWidgetMode: SignalLike = computed(() => this.inputs.widgets().length > 1); + + /** Whether navigation between widgets is disabled. */ + readonly navigationDisabled: SignalLike = computed( + () => !this.multiWidgetMode() || !this.active() || this.inputs.disabled(), + ); + + /** The focus behavior for the widgets in the cell. */ + readonly focusBehavior: ListFocus; + + /** The navigation behavior for the widgets in the cell. */ + readonly navigationBehavior: ListNavigation; + + /** The currently active widget in the cell. */ + readonly activeWidget: WritableSignalLike = linkedSignal(() => + this.inputs.widgets().length > 0 ? this.inputs.widgets()[0] : undefined, + ); + + /** Whether navigation between widgets is activated. */ + readonly navigationActivated: WritableSignalLike = signal(false); + + /** Whether any widget within the cell is activated. */ + readonly widgetActivated: SignalLike = computed(() => + this.inputs.widgets().some(w => w.isActivated()), + ); /** Whether the cell or widget inside the cell is activated. */ - readonly isActivated: SignalLike = computed(() => this.widget()?.isActivated() ?? false); + readonly isActivated: SignalLike = computed( + () => this.navigationActivated() || this.widgetActivated(), + ); + + /** The key used to navigate to the previous widget. */ + readonly prevKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowUp'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + }); + + /** The key used to navigate to the next widget. */ + readonly nextKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowDown'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + }); + + /** The keyboard event manager for the cell. */ + readonly keydown = computed(() => { + const manager = new KeyboardEventManager(); + + // Before start list navigation. + if (!this.navigationActivated()) { + manager.on('Enter', () => this.startNavigation()); + return manager; + } + + // Start list navigation. + manager + .on('Escape', () => this.stopNavigation()) + .on( + this.prevKey(), + () => this._advance(() => this.navigationBehavior.prev({focusElement: false})), + {ignoreRepeat: false}, + ) + .on( + this.nextKey(), + () => this._advance(() => this.navigationBehavior.next({focusElement: false})), + {ignoreRepeat: false}, + ) + .on('Home', () => this._advance(() => this.navigationBehavior.next({focusElement: false}))) + .on('End', () => this._advance(() => this.navigationBehavior.next({focusElement: false}))); + + return manager; + }); constructor(readonly inputs: GridCellInputs) { this.selected = inputs.selected; + + const listNavigationInputs: ListNavigationInputs = { + ...inputs, + items: inputs.widgets, + activeItem: this.activeWidget, + disabled: this.navigationDisabled, + focusMode: () => 'roving', + softDisabled: () => true, + }; + + this.focusBehavior = new ListFocus(listNavigationInputs); + this.navigationBehavior = new ListNavigation({ + ...listNavigationInputs, + focusManager: this.focusBehavior, + }); } /** Handles keydown events for the cell. */ onKeydown(event: KeyboardEvent): void { - if (this.disabled()) return; - this.widget()?.onKeydown(event); + if (this.disabled() || this.inputs.widgets().length === 0) return; + + // No navigation needed if single widget. + if (this.singleWidgetMode()) { + this.activeWidget()!.onKeydown(event); + return; + } + + // Focus is on the cell before the navigation starts. + if (!this.navigationActivated()) { + this.keydown().handle(event); + return; + } + + // Widget activate state can be changed during the widget keydown handling. + const widgetActivated = this.widgetActivated(); + + this.activeWidget()!.onKeydown(event); + + if (!widgetActivated) { + this.keydown().handle(event); + } } /** Handles focusin events for the cell. */ @@ -129,6 +252,19 @@ export class GridCellPattern implements GridCell { // Pass down focusin event to the widget. widget.onFocusIn(event); + + // Update internal states if the widget(or anything within the widget) is + // receiving focus by tabbing, pointer, or any programmatic control. + + // Update current active widget. + if (widget !== this.activeWidget()) { + this.navigationBehavior.goto(widget, {focusElement: false}); + } + + // Start widget navigation if multi widget. + if (this.multiWidgetMode()) { + this.navigationActivated.set(true); + } } /** Handles focusout events for the cell. */ @@ -143,13 +279,14 @@ export class GridCellPattern implements GridCell { if (this.element().contains(focusTarget)) return; this.isFocused.set(false); + // Reset navigation state when focus leaving cell. + this.navigationActivated.set(false); } /** Focuses the cell or the active widget. */ focus(): void { - const widget = this.widget(); - if (widget) { - widget.focus(); + if (this.singleWidgetMode()) { + this.activeWidget()?.focus(); } else { this.element().focus(); } @@ -157,6 +294,33 @@ export class GridCellPattern implements GridCell { /** Gets the tab index for the widget within the cell. */ widgetTabIndex(): -1 | 0 { - return this._tabIndex(); + if (this.singleWidgetMode()) { + return this._tabIndex(); + } + return this.navigationActivated() ? 0 : -1; + } + + /** Starts navigation between widgets. */ + startNavigation(): void { + if (this.navigationActivated()) return; + + this.navigationActivated.set(true); + this.navigationBehavior.first(); + } + + /** Stops navigation between widgets and restores focus to the cell. */ + stopNavigation(): void { + if (!this.navigationActivated()) return; + + this.navigationActivated.set(false); + this.element().focus(); + } + + /** Executes a navigation operation and focuses the new active widget. */ + private _advance(op: () => boolean): void { + const success = op(); + if (success) { + this.activeWidget()?.focus(); + } } } diff --git a/src/aria/private/grid/grid.spec.ts b/src/aria/private/grid/grid.spec.ts index 9af2475eeab9..9ec979dff747 100644 --- a/src/aria/private/grid/grid.spec.ts +++ b/src/aria/private/grid/grid.spec.ts @@ -37,6 +37,12 @@ const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', m const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); const escape = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 27, 'Escape', mods); +const shiftUp = () => up({shift: true}); +const shiftDown = () => down({shift: true}); +const shiftLeft = () => left({shift: true}); +const shiftRight = () => right({shift: true}); +const shiftHome = () => home({shift: true}); +const shiftEnd = () => end({shift: true}); function createClickEvent(element: HTMLElement, mods?: ModifierKeys): PointerEvent { return { @@ -51,13 +57,14 @@ function createClickEvent(element: HTMLElement, mods?: ModifierKeys): PointerEve } interface TestWidgetData { + id?: string; widgetType?: 'simple' | 'complex' | 'editable'; disabled?: boolean; } interface TestCellData { id?: string; - widget?: TestWidgetData; + widgets?: TestWidgetData[]; selectable?: boolean; disabled?: boolean; rowSpan?: number; @@ -88,7 +95,8 @@ function createGridRows(grid: GridPattern, data: TestRowData[]) { element: signal(document.createElement('div')), grid: signal(grid), row: signal(row), - widget: signal(undefined), + widgets: signal([]), + wrap: signal(false), rowIndex: signal(cellData.rowIndex), colIndex: signal(cellData.colIndex), selectable: signal(cellData.selectable ?? true), @@ -96,23 +104,27 @@ function createGridRows(grid: GridPattern, data: TestRowData[]) { rowSpan: signal(cellData.rowSpan ?? 1), colSpan: signal(cellData.colSpan ?? 1), selected: signal(cellData.selected ?? false), + orientation: signal('vertical'), + textDirection: signal('ltr'), getWidget: (el: Element | null) => builtWidgets.find(w => w.element() === el), }; const cell = new GridCellPattern(cellInputs); - if (cellData.widget) { + const cellWidgets = (cellData.widgets ?? []).map((widgetData, widgetIndex) => { const widgetInputs: TestGridCellWidgetInputs = { + id: signal(widgetData.id ?? `widget-${rowIndex}-${colIndex}-${widgetIndex}`), cell: signal(cell), element: signal(document.createElement('div')), - widgetType: signal(cellData.widget.widgetType ?? 'simple'), + widgetType: signal(widgetData.widgetType ?? 'simple'), focusTarget: signal(undefined), - disabled: signal(cellData.widget.disabled ?? false), + disabled: signal(widgetData.disabled ?? false), }; const widget = new GridCellWidgetPattern(widgetInputs); builtWidgets.push(widget); - cellInputs.widget.set(widget); - } + return widget; + }); + cellInputs.widgets.set(cellWidgets); return cell; }); @@ -129,6 +141,7 @@ function getDefaultGridInputs(): TestGridInputs { enableSelection: signal(false), multi: signal(false), selectionMode: signal('follow'), + enableRangeSelection: signal(false), getCell: () => undefined, focusMode: signal('roving'), disabled: signal(false), @@ -169,9 +182,10 @@ describe('Grid', () => { gridInputs = getDefaultGridInputs(); }); - it('should have correct initial properties for a widget pattern', () => { - const {grid} = createGrid([{cells: [{widget: {}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + it('should have correct initial properties', () => { + const {grid} = createGrid([{cells: [{widgets: [{id: 'test-id'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; + expect(widget.id()).toBe('test-id'); expect(widget.isActivated()).toBe(false); expect(widget.disabled()).toBe(false); }); @@ -179,8 +193,8 @@ describe('Grid', () => { it('should compute element and widgetHost correctly', () => { const element = document.createElement('div'); const focusTarget = document.createElement('button'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'simple'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'simple'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; const widgetInputs = widget.inputs as TestGridCellWidgetInputs; widgetInputs.element.set(element); widgetInputs.focusTarget.set(focusTarget); @@ -192,10 +206,20 @@ describe('Grid', () => { expect(widget.widgetHost()).toBe(element); }); + it('should compute index correctly', () => { + const {grid} = createGrid( + [{cells: [{widgets: [{id: 'widget-1'}, {id: 'widget-2'}]}]}], + gridInputs, + ); + const widgets = grid.cells()[0][0].inputs.widgets(); + expect(widgets[0].index()).toBe(0); + expect(widgets[1].index()).toBe(1); + }); + it('should be disabled if cell is disabled', () => { - const {grid} = createGrid([{cells: [{widget: {disabled: false}}]}], gridInputs); + const {grid} = createGrid([{cells: [{widgets: [{disabled: false}]}]}], gridInputs); const cell = grid.cells()[0][0]; - const widget = cell.inputs.widget()!; + const widget = cell.inputs.widgets()[0]; const cellInputs = cell.inputs as TestGridCellInputs; cellInputs.disabled.set(true); @@ -204,8 +228,8 @@ describe('Grid', () => { describe('Activation', () => { it('should activate and deactivate manually', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'complex'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.activate(); expect(widget.isActivated()).toBe(true); widget.deactivate(); @@ -213,8 +237,8 @@ describe('Grid', () => { }); it('should not activate if widgetType is simple', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'simple'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'simple'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.activate(); expect(widget.isActivated()).toBe(false); }); @@ -223,8 +247,8 @@ describe('Grid', () => { const host = document.createElement('div'); const inner = document.createElement('button'); host.appendChild(inner); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'complex'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; (widget.inputs as TestGridCellWidgetInputs).focusTarget.set(host); widget.onFocusIn({target: inner} as unknown as FocusEvent); @@ -233,8 +257,8 @@ describe('Grid', () => { it('should deactivate on focusout if focus leaves widgetHost', () => { const host = document.createElement('div'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'complex'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; (widget.inputs as TestGridCellWidgetInputs).focusTarget.set(host); widget.activate(); @@ -245,41 +269,41 @@ describe('Grid', () => { describe('Keyboard Events', () => { it('should activate on Enter for complex widget', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'complex'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.onKeydown(enter()); expect(widget.isActivated()).toBe(true); }); it('should deactivate on Escape when activated', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'complex'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.activate(); widget.onKeydown(escape()); expect(widget.isActivated()).toBe(false); }); it('should deactivate on Enter for editable widget when activated', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'editable'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'editable'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.activate(); widget.onKeydown(enter()); expect(widget.isActivated()).toBe(false); }); it('should activate on character key for editable widget', () => { - const {grid} = createGrid([{cells: [{widget: {widgetType: 'editable'}}]}], gridInputs); - const widget = grid.cells()[0][0].inputs.widget()!; + const {grid} = createGrid([{cells: [{widgets: [{widgetType: 'editable'}]}]}], gridInputs); + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.onKeydown(a()); expect(widget.isActivated()).toBe(true); }); it('should not activate if disabled', () => { const {grid} = createGrid( - [{cells: [{widget: {widgetType: 'complex', disabled: true}}]}], + [{cells: [{widgets: [{widgetType: 'complex', disabled: true}]}]}], gridInputs, ); - const widget = grid.cells()[0][0].inputs.widget()!; + const widget = grid.cells()[0][0].inputs.widgets()[0]; widget.onKeydown(enter()); expect(widget.isActivated()).toBe(false); }); @@ -293,7 +317,7 @@ describe('Grid', () => { gridInputs = getDefaultGridInputs(); }); - it('should have correct initial properties for a cell pattern', () => { + it('should have correct initial properties', () => { const {grid} = createGrid([{cells: [{id: 'test-cell'}]}], gridInputs); const cell = grid.cells()[0][0]; expect(cell.id()).toBe('test-cell'); @@ -346,68 +370,122 @@ describe('Grid', () => { expect(cell.tabIndex()).toBe(0); }); - it('should be -1 if cell contains a widget', () => { - const {grid} = createGrid([{cells: [{widget: {}}]}], gridInputs); + it('should be -1 if navigation is activated', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + grid.setDefaultStateEffect(); + cell.navigationActivated.set(true); + expect(cell.tabIndex()).toBe(-1); + }); + + it('should be -1 if in single widget mode', () => { + const {grid} = createGrid([{cells: [{widgets: [{}]}]}], gridInputs); const cell = grid.cells()[0][0]; grid.setDefaultStateEffect(); expect(cell.tabIndex()).toBe(-1); }); }); - describe('Keyboard Events', () => { - it('should delegate to widget', () => { - const {grid} = createGrid([{cells: [{widget: {}}]}], gridInputs); + describe('Widget Modes', () => { + it('should detect single widget mode', () => { + const {grid} = createGrid([{cells: [{widgets: [{}]}]}], gridInputs); const cell = grid.cells()[0][0]; - const widget = cell.inputs.widget()!; - spyOn(widget, 'onKeydown'); + expect(cell.singleWidgetMode()).toBe(true); + expect(cell.multiWidgetMode()).toBe(false); + }); - const event = enter(); - cell.onKeydown(event); - expect(widget.onKeydown).toHaveBeenCalledWith(event); + it('should detect multi widget mode', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + expect(cell.singleWidgetMode()).toBe(false); + expect(cell.multiWidgetMode()).toBe(true); }); + }); - it('should call onActivate on Enter for simple widget', () => { - const onActivateSpy = jasmine.createSpy('onActivate'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'simple'}}]}], gridInputs); + describe('Navigation', () => { + it('should start and stop navigation', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); const cell = grid.cells()[0][0]; - cell.inputs.widget()!.inputs.onActivate = onActivateSpy; + cell.startNavigation(); + expect(cell.navigationActivated()).toBe(true); + cell.stopNavigation(); + expect(cell.navigationActivated()).toBe(false); + }); - const event = enter(); - cell.onKeydown(event); - expect(onActivateSpy).toHaveBeenCalledWith(event); + it('should focus element on stop navigation', () => { + const element = document.createElement('div'); + spyOn(element, 'focus'); + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + (cell.inputs as TestGridCellInputs).element.set(element); + + cell.startNavigation(); + cell.stopNavigation(); + expect(element.focus).toHaveBeenCalled(); }); + }); - it('should call onActivate on Space for simple widget', () => { - const onActivateSpy = jasmine.createSpy('onActivate'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'simple'}}]}], gridInputs); + describe('Keyboard Events', () => { + it('should start navigation on Enter in multi-widget mode', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); const cell = grid.cells()[0][0]; - cell.inputs.widget()!.inputs.onActivate = onActivateSpy; + cell.onKeydown(enter()); + expect(cell.navigationActivated()).toBe(true); + }); - const event = space(); - cell.onKeydown(event); - expect(onActivateSpy).toHaveBeenCalledWith(event); + it('should stop navigation on Escape', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + cell.startNavigation(); + cell.onKeydown(escape()); + expect(cell.navigationActivated()).toBe(false); }); - it('should NOT call onActivate for complex widget', () => { - const onActivateSpy = jasmine.createSpy('onActivate'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'complex'}}]}], gridInputs); + it('should delegate to active widget in single widget mode', () => { + const {grid} = createGrid([{cells: [{widgets: [{}]}]}], gridInputs); const cell = grid.cells()[0][0]; - cell.inputs.widget()!.inputs.onActivate = onActivateSpy; + const widget = cell.inputs.widgets()[0]; + spyOn(widget, 'onKeydown'); const event = enter(); cell.onKeydown(event); - expect(onActivateSpy).not.toHaveBeenCalled(); + expect(widget.onKeydown).toHaveBeenCalledWith(event); }); - it('should NOT call onActivate for other keys', () => { - const onActivateSpy = jasmine.createSpy('onActivate'); - const {grid} = createGrid([{cells: [{widget: {widgetType: 'simple'}}]}], gridInputs); + it('should navigate widgets on arrow keys during navigation', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); const cell = grid.cells()[0][0]; - cell.inputs.widget()!.inputs.onActivate = onActivateSpy; + const widget = cell.inputs.widgets()[1]; + spyOn(widget, 'focus'); - const event = up(); - cell.onKeydown(event); - expect(onActivateSpy).not.toHaveBeenCalled(); + grid.gridBehavior.focusBehavior.focusCell(cell); + cell.startNavigation(); + cell.onKeydown(down()); + expect(cell.activeWidget()).toBe(widget); + expect(widget.focus).toHaveBeenCalled(); + }); + }); + + describe('Focus Events', () => { + it('should update active widget and start navigation on focusin', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + const w2 = cell.inputs.widgets()[1]; + spyOn(w2, 'onFocusIn'); + + grid.gridBehavior.focusBehavior.focusCell(cell); + cell.onFocusIn({target: w2.element()} as unknown as FocusEvent); + expect(cell.activeWidget()).toBe(w2); + expect(cell.navigationActivated()).toBe(true); + expect(w2.onFocusIn).toHaveBeenCalled(); + }); + + it('should reset navigation state on focusout', () => { + const {grid} = createGrid([{cells: [{widgets: [{}, {}]}]}], gridInputs); + const cell = grid.cells()[0][0]; + cell.startNavigation(); + cell.onFocusOut({target: document.createElement('div')} as unknown as FocusEvent); + expect(cell.navigationActivated()).toBe(false); }); }); }); @@ -429,28 +507,28 @@ describe('Grid', () => { grid.setDefaultStateEffect(); }); - it('should navigate focus to the cell above on ArrowUp', () => { + it('should navigate up on ArrowUp', () => { const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); // Center cell grid.onKeydown(up()); expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[0][1]); }); - it('should navigate focus to the cell below on ArrowDown', () => { + it('should navigate down on ArrowDown', () => { const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); grid.onKeydown(down()); expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[2][1]); }); - it('should navigate focus to the cell to the left on ArrowLeft', () => { + it('should navigate left on ArrowLeft', () => { const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); grid.onKeydown(left()); expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[1][0]); }); - it('should navigate focus to the cell to the right on ArrowRight', () => { + it('should navigate right on ArrowRight', () => { const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); grid.onKeydown(right()); @@ -496,7 +574,7 @@ describe('Grid', () => { grid.setDefaultStateEffect(); }); - it('should update selection to follow focus in follow mode', () => { + it('should follow focus in follow mode', () => { (gridInputs.selectionMode as WritableSignalLike<'follow' | 'explicit'>).set('follow'); const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[0][0]); @@ -504,7 +582,7 @@ describe('Grid', () => { expect(cells[1][0].selected()).toBe(true); }); - it('should not update selection to follow focus in explicit mode', () => { + it('should not follow focus in explicit mode', () => { (gridInputs.selectionMode as WritableSignalLike<'follow' | 'explicit'>).set('explicit'); const cells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(cells[0][0]); @@ -524,6 +602,7 @@ describe('Grid', () => { it('should select all on Ctrl+A', () => { (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); grid.onKeydown(a({control: true})); expect( grid @@ -535,6 +614,7 @@ describe('Grid', () => { it('should select row on Shift+Space', () => { (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); const gridCells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]); grid.onKeydown(space({shift: true})); @@ -547,6 +627,7 @@ describe('Grid', () => { it('should select column on Ctrl+Space', () => { (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); const gridCells = grid.cells(); grid.gridBehavior.focusBehavior.focusCell(gridCells[0][0]); grid.onKeydown(space({control: true})); @@ -557,9 +638,69 @@ describe('Grid', () => { expect(gridCells[1][1].selected()).toBe(false); }); }); + + describe('Range Selection Logic', () => { + let grid: GridPattern; + + beforeEach(() => { + (gridInputs.enableSelection as WritableSignalLike).set(true); + (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); + + const data = [{cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}, {cells: [{}, {}, {}]}]; + const result = createGrid(data, gridInputs); + grid = result.grid; + grid.setDefaultStateEffect(); + }); + + it('should select range up on Shift+ArrowUp', () => { + const cells = grid.cells(); + grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); + grid.onKeydown(shiftUp()); + expect(cells[1][1].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(true); + }); + + it('should select range down on Shift+ArrowDown', () => { + const cells = grid.cells(); + grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); + grid.onKeydown(shiftDown()); + expect(cells[1][1].selected()).toBe(true); + expect(cells[2][1].selected()).toBe(true); + }); + + it('should select range left on Shift+ArrowLeft', () => { + const cells = grid.cells(); + grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); + grid.onKeydown(shiftLeft()); + expect(cells[1][1].selected()).toBe(true); + expect(cells[1][0].selected()).toBe(true); + }); + + it('should select range right on Shift+ArrowRight', () => { + const cells = grid.cells(); + grid.gridBehavior.focusBehavior.focusCell(cells[1][1]); + grid.onKeydown(shiftRight()); + expect(cells[1][1].selected()).toBe(true); + expect(cells[1][2].selected()).toBe(true); + }); + + it('should support range selection with Shift+Home/End', () => { + const cells = grid.cells(); + grid.gridBehavior.focusBehavior.focusCell(cells[0][1]); + grid.onKeydown(shiftHome()); + expect(cells[0][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(true); + + grid.onKeydown(shiftEnd()); + expect(cells[0][0].selected()).toBe(false); + expect(cells[0][1].selected()).toBe(true); + expect(cells[0][2].selected()).toBe(true); + }); + }); }); - describe('Click Events', () => { + describe('Pointer Events', () => { let grid: GridPattern; beforeEach(() => { @@ -570,40 +711,76 @@ describe('Grid', () => { grid.setDefaultStateEffect(); }); - describe('Basic Click Actions', () => { - it('should move focus to the clicked cell on click', () => { + describe('Basic Pointer Actions', () => { + it('should focus cell on pointerdown', () => { const cells = grid.cells(); - grid.onClick(createClickEvent(cells[0][1].element())); + grid.onPointerdown(createClickEvent(cells[0][1].element())); expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[0][1]); }); }); - describe('Click Selection', () => { - it('should follow focus in follow mode on click', () => { + describe('Pointer Selection', () => { + it('should follow focus in follow mode on pointerdown', () => { (gridInputs.selectionMode as WritableSignalLike<'follow' | 'explicit'>).set('follow'); const cell = grid.cells()[0][1]; - grid.onClick(createClickEvent(cell.element())); + grid.onPointerdown(createClickEvent(cell.element())); expect(cell.selected()).toBe(true); }); - it('should toggle selection in explicit mode on click', () => { + it('should toggle selection in explicit mode on pointerdown', () => { (gridInputs.selectionMode as WritableSignalLike<'follow' | 'explicit'>).set('explicit'); const cell = grid.cells()[0][1]; - grid.onClick(createClickEvent(cell.element())); + grid.onPointerdown(createClickEvent(cell.element())); expect(cell.selected()).toBe(true); - grid.onClick(createClickEvent(cell.element())); + grid.onPointerdown(createClickEvent(cell.element())); expect(cell.selected()).toBe(false); }); - it('should support multi-selection with Ctrl+click', () => { + it('should support multi-selection with Ctrl+pointerdown', () => { (gridInputs.multi as WritableSignalLike).set(true); const cells = grid.cells(); - grid.onClick(createClickEvent(cells[0][0].element())); - grid.onClick(createClickEvent(cells[0][1].element(), {control: true})); + grid.onPointerdown(createClickEvent(cells[0][0].element())); + grid.onPointerdown(createClickEvent(cells[0][1].element(), {control: true})); expect(cells[0][0].selected()).toBe(true); expect(cells[0][1].selected()).toBe(true); }); + + it('should support range selection with Shift+pointerdown', () => { + (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); + const cells = grid.cells(); + grid.onPointerdown(createClickEvent(cells[0][0].element())); + grid.onPointerdown(createClickEvent(cells[1][1].element(), {shift: true})); + expect(cells[0][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(true); + expect(cells[1][0].selected()).toBe(true); + expect(cells[1][1].selected()).toBe(true); + }); + }); + + describe('Range Selection Dragging', () => { + beforeEach(() => { + (gridInputs.multi as WritableSignalLike).set(true); + (gridInputs.enableRangeSelection as WritableSignalLike).set(true); + }); + + it('should select range on pointermove', () => { + const cells = grid.cells(); + grid.onPointerdown(createClickEvent(cells[0][0].element())); + grid.onPointermove(createClickEvent(cells[0][1].element())); + expect(cells[0][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(true); + }); + + it('should stabilize selection on pointerup', () => { + const cell = grid.cells()[0][1]; + grid.onPointerdown(createClickEvent(grid.cells()[0][0].element())); + grid.onPointermove(createClickEvent(cell.element())); + expect(grid.dragging()).toBe(true); + grid.onPointerup(createClickEvent(cell.element())); + expect(grid.dragging()).toBe(false); + }); }); }); @@ -671,12 +848,12 @@ describe('Grid', () => { grid.setDefaultStateEffect(); }); - it('should set aria-activedescendant to the focused cell id on focus', () => { + it('should set aria-activedescendant on focus', () => { grid.onFocusIn({target: grid.inputs.element()} as unknown as FocusEvent); expect(grid.activeDescendant()).toBe(grid.cells()[0][0].id()); }); - it('should update aria-activedescendant to the new cell id on navigation', () => { + it('should update aria-activedescendant on navigation', () => { grid.onFocusIn({target: grid.inputs.element()} as unknown as FocusEvent); grid.onKeydown(down()); expect(grid.activeDescendant()).toBe(grid.cells()[1][0].id()); @@ -684,53 +861,4 @@ describe('Grid', () => { }); }); }); - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const gridInputs = getDefaultGridInputs(); - const data = [{cells: [{}, {}]}, {cells: [{}, {}]}]; - const {grid} = createGrid(data, gridInputs); - const cells = grid.cells(); - cells[0][1].inputs.selected.set(true); // default state will pick this since it's selected - grid.setDefaultStateEffect(); - expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[0][1]); // Should set to selected cell - }); - - it('should NOT set default state if keyboard interacted', () => { - const gridInputs = getDefaultGridInputs(); - const data = [{cells: [{}, {}]}, {cells: [{}, {}]}]; - const {grid} = createGrid(data, gridInputs); - grid.onKeydown(down()); // Interaction - - const cells = grid.cells(); - cells[0][1].inputs.selected.set(true); - grid.setDefaultStateEffect(); - expect(grid.gridBehavior.focusBehavior.activeCell()).toBeUndefined(); // Should stay undefined, meaning default state was skipped - }); - - it('should NOT set default state if click interacted', () => { - const gridInputs = getDefaultGridInputs(); - const data = [{cells: [{}, {}]}, {cells: [{}, {}]}]; - const {grid} = createGrid(data, gridInputs); - const cells = grid.cells(); - grid.onClick({target: cells[0][0].element()} as unknown as PointerEvent); // Interaction - - cells[0][1].inputs.selected.set(true); - grid.setDefaultStateEffect(); - expect(grid.gridBehavior.focusBehavior.activeCell()).toBe(cells[0][0]); // Should stay on interacted cell - }); - - it('should NOT set default state if focus-in occurred', () => { - const gridInputs = getDefaultGridInputs(); - const data = [{cells: [{}, {}]}, {cells: [{}, {}]}]; - const {grid} = createGrid(data, gridInputs); - - grid.onFocusIn({} as FocusEvent); // Interaction - - const cells = grid.cells(); - cells[0][1].inputs.selected.set(true); - grid.setDefaultStateEffect(); - expect(grid.gridBehavior.focusBehavior.activeCell()).toBeUndefined(); // Should stay undefined due to early return - }); - }); }); diff --git a/src/aria/private/grid/grid.ts b/src/aria/private/grid/grid.ts index 6c2b746c3c0e..030d2a71c3b7 100644 --- a/src/aria/private/grid/grid.ts +++ b/src/aria/private/grid/grid.ts @@ -7,7 +7,7 @@ */ import {SignalLike, computed, signal, untracked} from '../behaviors/signal-like/signal-like'; -import {KeyboardEventManager, ClickEventManager, Modifier} from '../behaviors/event-manager'; +import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; import {NavOptions, Grid, GridInputs as GridBehaviorInputs} from '../behaviors/grid'; import type {GridRowPattern} from './row'; import type {GridCellPattern} from './cell'; @@ -32,6 +32,9 @@ export interface GridInputs extends Omit, 'c /** The selection strategy used by the grid. */ selectionMode: SignalLike<'follow' | 'explicit'>; + /** Whether enable range selection. */ + enableRangeSelection: SignalLike; + /** A function that returns the grid cell associated with a given element. */ getCell: (e: Element | null) => GridCellPattern | undefined; } @@ -50,11 +53,6 @@ export class GridPattern { /** Whether the grid is disabled. */ readonly disabled = computed(() => this.gridBehavior.gridDisabled()); - /** Whether the grid is multi-selectable. */ - readonly multiSelectable = computed(() => - this.inputs.enableSelection() ? this.inputs.multi() : undefined, - ); - /** The ID of the currently active descendant cell. */ readonly activeDescendant = computed(() => this.gridBehavior.activeDescendant()); @@ -63,7 +61,9 @@ export class GridPattern { /** The current selection anchor cell. */ readonly anchorCell: SignalLike = computed(() => - this.multiSelectable() ? this.gridBehavior.selectionAnchorCell() : undefined, + this.inputs.enableSelection() && this.inputs.multi() + ? this.gridBehavior.selectionAnchorCell() + : undefined, ); /** Whether to pause grid navigation and give the keyboard control to cell or widget. */ @@ -77,8 +77,8 @@ export class GridPattern { /** Whether the focus is in the grid. */ readonly isFocused = signal(false); - /** Whether the grid has received focus once. */ - readonly hasBeenInteracted = signal(false); + /** Whether the grid has been focused once. */ + readonly hasBeenFocused = signal(false); /** Whether the user is currently dragging to select a range of cells. */ readonly dragging = signal(false); @@ -93,6 +93,16 @@ export class GridPattern { this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight', ); + /** Whether the grid pattern is currently accepting `pointermove` events. */ + readonly acceptsPointerMove = computed(() => { + return ( + !this.disabled() && + this.inputs.enableSelection() && + this.inputs.enableRangeSelection() && + this.dragging() + ); + }); + /** The keydown event manager for the grid. */ readonly keydown = computed(() => { const manager = new KeyboardEventManager(); @@ -122,9 +132,17 @@ export class GridPattern { ); } - // Explicit multiselection modifier handlers. - if (this.inputs.enableSelection() && this.inputs.multi()) { + // Range selection handlers. + if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { manager + .on(Modifier.Shift, 'ArrowUp', () => this.gridBehavior.up({anchor: true})) + .on(Modifier.Shift, 'ArrowDown', () => this.gridBehavior.down({anchor: true})) + .on(Modifier.Shift, this.prevColKey(), () => this.gridBehavior.left({anchor: true})) + .on(Modifier.Shift, this.nextColKey(), () => this.gridBehavior.right({anchor: true})) + .on(Modifier.Shift, 'Home', () => this.gridBehavior.firstInRow({anchor: true})) + .on(Modifier.Shift, 'End', () => this.gridBehavior.lastInRow({anchor: true})) + .on([Modifier.Ctrl | Modifier.Shift], 'Home', () => this.gridBehavior.first({anchor: true})) + .on([Modifier.Ctrl | Modifier.Shift], 'End', () => this.gridBehavior.last({anchor: true})) .on([Modifier.Ctrl, Modifier.Meta], 'A', () => { if (this.gridBehavior.allSelected()) { this.gridBehavior.deselectAll(); @@ -139,9 +157,9 @@ export class GridPattern { return manager; }); - /** The click event manager for the grid. */ - readonly clickManager = computed(() => { - const manager = new ClickEventManager(); + /** The pointerdown event manager for the grid. */ + readonly pointerdown = computed(() => { + const manager = new PointerEventManager(); // Navigation without selection. if (!this.inputs.enableSelection()) { @@ -164,6 +182,10 @@ export class GridPattern { toggleOne: this.inputs.selectionMode() === 'explicit' && !this.inputs.multi(), toggle: this.inputs.selectionMode() === 'explicit' && this.inputs.multi(), }); + + if (this.inputs.multi() && this.inputs.enableRangeSelection()) { + this.dragging.set(true); + } }); // Selection with modifier keys. @@ -173,13 +195,40 @@ export class GridPattern { if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; this.gridBehavior.gotoCell(cell, {toggle: true}); + + if (this.inputs.enableRangeSelection()) { + this.dragging.set(true); + } }); + + if (this.inputs.enableRangeSelection()) { + manager.on(Modifier.Shift, e => { + const cell = this.inputs.getCell(e.target as Element); + if (!cell) return; + + this.gridBehavior.gotoCell(cell, {anchor: true}); + this.dragging.set(true); + }); + } } } return manager; }); + /** The pointerup event manager for the grid. */ + readonly pointerup = computed(() => { + const manager = new PointerEventManager(); + + if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { + manager.on([Modifier.Shift, Modifier.Ctrl, Modifier.Meta, Modifier.None], () => { + this.dragging.set(false); + }); + } + + return manager; + }); + /** Indicates maybe the losing focus is caused by row/cell deletion. */ private readonly _maybeDeletion = signal(false); @@ -196,41 +245,47 @@ export class GridPattern { }); } - /** Returns a set of violations */ - validate(): string[] { - const violations: string[] = []; - - const rows = this.inputs.rows(); - for (const row of rows) { - if (row.inputs.cells().length === 0) { - violations.push('ngGridRow must contain at least one ngGridCell.'); - } - } - - return violations; - } - /** Handles keydown events on the grid. */ onKeydown(event: KeyboardEvent) { if (this.disabled()) return; - this.hasBeenInteracted.set(true); this.activeCell()?.onKeydown(event); this.keydown().handle(event); } - /** Handles click events on the grid. */ - onClick(event: PointerEvent) { + /** Handles pointerdown events on the grid. */ + onPointerdown(event: PointerEvent) { if (this.disabled()) return; - this.hasBeenInteracted.set(true); - this.clickManager().handle(event); + this.pointerdown().handle(event); + } + + /** Handles pointermove events on the grid. */ + onPointermove(event: PointerEvent) { + if (this.acceptsPointerMove()) { + const cell = this.inputs.getCell(event.target as Element); + + // Dragging anchor. + if (cell !== undefined) { + this.gridBehavior.gotoCell(cell, {anchor: true}); + } + } + } + + /** Handles pointerup events on the grid. */ + onPointerup(event: PointerEvent) { + if (this.disabled()) return; + + this.pointerup().handle(event); } /** Handles focusin events on the grid. */ onFocusIn(event: FocusEvent) { this.isFocused.set(true); - this.hasBeenInteracted.set(true); + this.hasBeenFocused.set(true); + + // Skip if in the middle of range selection. + if (this.dragging()) return; // Cell that receives focus. const cell = this.inputs.getCell(event.target as Element | null); @@ -270,7 +325,8 @@ export class GridPattern { /** Sets the default active state of the grid before receiving focus the first time. */ setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; + if (this.hasBeenFocused()) return; + this.gridBehavior.setDefaultState(); } diff --git a/src/aria/private/grid/widget.ts b/src/aria/private/grid/widget.ts index 56480bf55582..b5e810b35f7a 100644 --- a/src/aria/private/grid/widget.ts +++ b/src/aria/private/grid/widget.ts @@ -7,6 +7,7 @@ */ import {KeyboardEventManager, Modifier} from '../behaviors/event-manager'; +import {ListNavigationItem} from '../behaviors/list-navigation/list-navigation'; import { SignalLike, computed, @@ -14,13 +15,9 @@ import { WritableSignalLike, } from '../behaviors/signal-like/signal-like'; import type {GridCellPattern} from './cell'; -import {ElementResolver, resolveElement} from '../utils/element-resolver'; /** The inputs for the `GridCellWidgetPattern`. */ -export interface GridCellWidgetInputs { - /** Whether the widget is disabled. */ - disabled: SignalLike; - +export interface GridCellWidgetInputs extends Omit { /** The `GridCellPattern` that this widget belongs to. */ cell: SignalLike; @@ -31,23 +28,26 @@ export interface GridCellWidgetInputs { widgetType: SignalLike<'simple' | 'complex' | 'editable'>; /** The element that will receive focus when the widget is activated. */ - focusTarget: SignalLike>; - - /** Callback hook used to notify parents or directives upon interaction. */ - onActivate?: (event: KeyboardEvent | FocusEvent | undefined) => void; - - /** Callback hook used to notify parents or directives upon exit. */ - onDeactivate?: (event: KeyboardEvent | FocusEvent | undefined) => void; + focusTarget: SignalLike; } /** The UI pattern for a widget inside a grid cell. */ -export class GridCellWidgetPattern { +export class GridCellWidgetPattern implements ListNavigationItem { + /** A unique identifier for the widget. */ + readonly id: SignalLike = () => this.inputs.id(); + /** The html element that should receive focus. */ readonly element: SignalLike = () => this.inputs.element(); /** The element that should receive focus. */ - readonly widgetHost: SignalLike = () => - resolveElement(this.inputs.focusTarget(), this.element()) ?? this.element(); + readonly widgetHost: SignalLike = computed( + () => this.inputs.focusTarget() ?? this.element(), + ); + + /** The index of the widget within the cell. */ + readonly index: SignalLike = computed(() => + this.inputs.cell().inputs.widgets().indexOf(this), + ); /** Whether the widget is disabled. */ readonly disabled: SignalLike = computed( @@ -55,17 +55,10 @@ export class GridCellWidgetPattern { ); /** The tab index for the widget. */ - readonly tabIndex: SignalLike<-1 | 0> = computed(() => { - if (this.inputs.focusTarget()) { - return -1; - } - return this.inputs.cell().widgetTabIndex(); - }); + readonly tabIndex: SignalLike<-1 | 0> = computed(() => this.inputs.cell().widgetTabIndex()); - /** Whether the widget is the active widget in the cell. */ - readonly active: SignalLike = computed( - () => this.inputs.cell().active() && this.inputs.cell().widget() === this, - ); + /** Whether the widget is the active item in the widget list. */ + readonly active: SignalLike = computed(() => this.inputs.cell().activeWidget() === this); /** Whether the widget is currently activated. */ readonly isActivated: WritableSignalLike = signal(false); @@ -82,25 +75,23 @@ export class GridCellWidgetPattern { readonly keydown = computed(() => { const manager = new KeyboardEventManager(); - // Simple widgets emit notification on interaction without capturing event flow + // Simple widget does not need to pause default grid behaviors. if (this.inputs.widgetType() === 'simple') { - return manager - .on('Enter', e => this.inputs.onActivate?.(e), { - preventDefault: false, - stopPropagation: false, - }) - .on(' ', e => this.inputs.onActivate?.(e), { - preventDefault: false, - stopPropagation: false, - }); + return manager; } // If a widget is activated, only listen to events that exits activate state. if (this.isActivated()) { - manager.on('Escape', e => this.deactivate(e)); + manager.on('Escape', e => { + this.deactivate(e); + this.focus(); + }); if (this.inputs.widgetType() === 'editable') { - manager.on('Enter', e => this.deactivate(e)); + manager.on('Enter', e => { + this.deactivate(e); + this.focus(); + }); } return manager; @@ -153,32 +144,6 @@ export class GridCellWidgetPattern { this.widgetHost().focus(); } - /** Side-effect executed whenever the widget activates. Runs in the write phase. */ - activationEffect(): void { - if (this.isActivated()) { - const event = this.lastActivateEvent(); - this.inputs.onActivate?.(event); - - // Only automatically redirect focus if explicit configuration was supplied. - if (this.inputs.focusTarget()) { - this.focus(); - } - } - } - - /** Side-effect executed whenever the widget deactivates. Runs in the write phase. */ - deactivationEffect(): void { - const event = this.lastDeactivateEvent(); - if (event) { - this.inputs.onDeactivate?.(event); - - // Only automatically restore focus if the deactivation was triggered by user keyboard interaction. - if (event instanceof KeyboardEvent) { - this.focus(); - } - } - } - /** Activates the widget. */ activate(event?: KeyboardEvent | FocusEvent): void { if (this.isActivated()) return; diff --git a/src/aria/private/listbox/BUILD.bazel b/src/aria/private/listbox/BUILD.bazel index 5c5fe8ecf3cc..92feb7008ac3 100644 --- a/src/aria/private/listbox/BUILD.bazel +++ b/src/aria/private/listbox/BUILD.bazel @@ -13,6 +13,7 @@ ts_project( "//src/aria/private/behaviors/event-manager", "//src/aria/private/behaviors/list", "//src/aria/private/behaviors/signal-like", + "//src/aria/private/combobox", ], ) diff --git a/src/aria/private/listbox/combobox-listbox.ts b/src/aria/private/listbox/combobox-listbox.ts new file mode 100644 index 000000000000..938cd3bd5a83 --- /dev/null +++ b/src/aria/private/listbox/combobox-listbox.ts @@ -0,0 +1,111 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ListboxInputs, ListboxPattern} from './listbox'; +import {SignalLike, computed} from '../behaviors/signal-like/signal-like'; +import {OptionPattern} from './option'; +import {ComboboxPattern, ComboboxListboxControls} from '../combobox/combobox'; + +export type ComboboxListboxInputs = ListboxInputs & { + /** The combobox controlling the listbox. */ + combobox: SignalLike, V> | undefined>; +}; + +export class ComboboxListboxPattern + extends ListboxPattern + implements ComboboxListboxControls, V> +{ + /** A unique identifier for the popup. */ + id = computed(() => this.inputs.id()); + + /** The ARIA role for the listbox. */ + role = computed(() => 'listbox' as const); + + /** The id of the active (focused) item in the listbox. */ + activeId = computed(() => this.listBehavior.activeDescendant()); + + /** The list of options in the listbox. */ + items: SignalLike[]> = computed(() => this.inputs.items()); + + /** The tab index for the listbox. Always -1 because the combobox handles focus. */ + override tabIndex: SignalLike<-1 | 0> = () => -1; + + /** Whether multiple items in the list can be selected at once. */ + override multi = computed(() => { + return this.inputs.combobox()?.readonly() ? this.inputs.multi() : false; + }); + + constructor(override readonly inputs: ComboboxListboxInputs) { + if (inputs.combobox()) { + inputs.focusMode = () => 'activedescendant'; + inputs.element = inputs.combobox()!.inputs.inputEl; + } + + super(inputs); + } + + /** Noop. The combobox handles keydown events. */ + override onKeydown(_: KeyboardEvent): void {} + + /** Noop. The combobox handles pointerdown events. */ + override onPointerdown(_: PointerEvent): void {} + + /** Noop. The combobox controls the open state. */ + override setDefaultState(): void {} + + /** Navigates to the specified item in the listbox. */ + focus = (item: OptionPattern, opts?: {focusElement?: boolean}) => { + this.listBehavior.goto(item, opts); + }; + + /** Navigates to the previous focusable item in the listbox. */ + getActiveItem = () => this.inputs.activeItem(); + + /** Navigates to the next focusable item in the listbox. */ + next = () => this.listBehavior.next(); + + /** Navigates to the previous focusable item in the listbox. */ + prev = () => this.listBehavior.prev(); + + /** Navigates to the last focusable item in the listbox. */ + last = () => this.listBehavior.last(); + + /** Navigates to the first focusable item in the listbox. */ + first = () => this.listBehavior.first(); + + /** Unfocuses the currently focused item in the listbox. */ + unfocus = () => this.listBehavior.unfocus(); + + /** Selects the specified item in the listbox. */ + select = (item?: OptionPattern) => this.listBehavior.select(item); + + /** Toggles the selection state of the given item in the listbox. */ + toggle = (item?: OptionPattern) => this.listBehavior.toggle(item); + + /** Clears the selection in the listbox. */ + clearSelection = () => this.listBehavior.deselectAll(); + + /** Retrieves the OptionPattern associated with a pointer event. */ + getItem = (e: PointerEvent) => this._getItem(e); + + /** Retrieves the currently selected items in the listbox. */ + getSelectedItems = () => { + // NOTE: We need to do this funky for loop to preserve the order of the selected values. + const items = []; + for (const value of this.inputs.values()) { + const item = this.items().find(i => i.value() === value); + if (item) { + items.push(item); + } + } + return items; + }; + + /** Sets the value of the combobox listbox. */ + setValue = (value: V | undefined) => this.inputs.values.set(value ? [value] : []); +} diff --git a/src/aria/private/listbox/listbox.spec.ts b/src/aria/private/listbox/listbox.spec.ts index 3471a2993584..ff4fc0ba3844 100644 --- a/src/aria/private/listbox/listbox.spec.ts +++ b/src/aria/private/listbox/listbox.spec.ts @@ -34,7 +34,7 @@ describe('Listbox Pattern', () => { return new ListboxPattern({ id: signal('listbox-1'), items: inputs.items, - value: inputs.value ?? signal([]), + values: inputs.values ?? signal([]), activeItem: signal(undefined), typeaheadDelay: inputs.typeaheadDelay ?? signal(500), wrap: inputs.wrap ?? signal(true), @@ -51,14 +51,14 @@ describe('Listbox Pattern', () => { } function getOptions(listbox: TestListbox, values: string[]): TestOption[] { - return values.map((val, index) => { + return values.map((value, index) => { const element = document.createElement('div'); element.role = 'option'; return new OptionPattern({ - value: signal(val), + value: signal(value), id: signal(`option-${index}`), disabled: signal(false), - searchTerm: signal(val), + searchTerm: signal(value), listbox: signal(listbox), element: signal(element), }); @@ -90,26 +90,6 @@ describe('Listbox Pattern', () => { ); } - describe('Tabindex', () => { - it('should expose tabIndex signals on listbox and options', () => { - const {listbox, options} = getDefaultPatterns(); - - expect(listbox.tabIndex()).toBe(-1); - expect(options[0].tabIndex()).toBe(0); - expect(options[1].tabIndex()).toBe(-1); - - listbox.onKeydown(down()); - - expect(options[0].tabIndex()).toBe(-1); - expect(options[1].tabIndex()).toBe(0); - }); - - it('should set tabindex 0 for listbox in activedescendant mode', () => { - const {listbox} = getDefaultPatterns({focusMode: signal('activedescendant')}); - expect(listbox.tabIndex()).toBe(0); - }); - }); - describe('Keyboard Navigation', () => { it('should navigate next on ArrowDown', () => { const {listbox} = getDefaultPatterns(); @@ -192,47 +172,47 @@ describe('Listbox Pattern', () => { describe('Keyboard Selection', () => { describe('follows focus & single select', () => { - it('should select an option on navigation (follows focus & single select)', () => { + it('should select an option on navigation', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), multi: signal(false), selectionMode: signal('follow'), }); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(up()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[8]); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); listbox.onKeydown(home()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should not be able to change selection when in readonly mode (follows focus & single select)', () => { + it('should not be able to change selection when in readonly mode', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), readonly: signal(true), multi: signal(false), selectionMode: signal('follow'), }); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); @@ -241,38 +221,38 @@ describe('Listbox Pattern', () => { beforeEach(() => { listbox = getDefaultPatterns({ - value: signal([]), + values: signal([]), selectionMode: signal('explicit'), multi: signal(false), }).listbox; }); - it('should select an option on Space (explicit focus & single select)', () => { + it('should select an option on Space', () => { listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should select an option on Enter (explicit focus & single select)', () => { + it('should select an option on Enter', () => { listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should only allow one selected option', () => { listbox.onKeydown(enter()); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); - it('should not be able to change selection when in readonly mode (explicit focus & single select)', () => { + it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignalLike; readonly.set(true); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); }); @@ -282,7 +262,7 @@ describe('Listbox Pattern', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), selectionMode: signal('explicit'), multi: signal(true), }); @@ -290,62 +270,62 @@ describe('Listbox Pattern', () => { options = patterns.options; }); - it('should select an option on Space (explicit focus & multi select)', () => { + it('should select an option on Space', () => { listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should select an option on Enter (explicit focus & multi select)', () => { + it('should select an option on Enter', () => { listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should allow multiple selected options', () => { listbox.onKeydown(enter()); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); }); - it('should select a range of options on Shift + ArrowDown/ArrowUp (explicit focus & multi select)', () => { + it('should select a range of options on Shift + ArrowDown/ArrowUp', () => { listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should not allow wrapping while Shift is held down (explicit focus & multi select)', () => { + it('should not allow wrapping while Shift is held down', () => { listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); - it('should select a range of options on Shift + Space (or Enter) (explicit focus & multi select)', () => { + it('should select a range of options on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(space()); // Apricot listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apricot', 'Banana', 'Blackberry']); + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana', 'Blackberry']); }); - it('should deselect options outside the range on subsequent on Shift + Space (or Enter) (explicit focus & multi select)', () => { + it('should deselect options outside the range on subsequent on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.inputs.values()).toEqual(['Banana']); listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); listbox.onKeydown(up()); listbox.onKeydown(up()); @@ -353,19 +333,19 @@ describe('Listbox Pattern', () => { listbox.onKeydown(up()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); - it('should select the focused option and all options up to the first option on Ctrl + Shift + Home (explicit focus & multi select)', () => { + it('should select the focused option and all options up to the first option on Ctrl + Shift + Home', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); }); - it('should select the focused option and all options down to the last option on Ctrl + Shift + End (explicit focus & multi select)', () => { + it('should select the focused option and all options down to the last option on Ctrl + Shift + End', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(down()); @@ -373,31 +353,36 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Cantaloupe', 'Cherry', 'Clementine', 'Cranberry']); + expect(listbox.inputs.values()).toEqual([ + 'Cantaloupe', + 'Cherry', + 'Clementine', + 'Cranberry', + ]); }); - it('should not be able to change selection when in readonly mode (explicit focus & multi select)', () => { + it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignalLike; readonly.set(true); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should not change the selected state of disabled options on Shift + ArrowUp / ArrowDown', () => { @@ -405,19 +390,19 @@ describe('Listbox Pattern', () => { options[1].disabled.set(true); listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select all options on Ctrl + A', () => { - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual([ + expect(listbox.inputs.values()).toEqual([ 'Apple', 'Apricot', 'Banana', @@ -431,10 +416,10 @@ describe('Listbox Pattern', () => { }); it('should deselect all options on Ctrl + A if all options are selected', () => { - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(a({control: true})); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); }); @@ -444,7 +429,7 @@ describe('Listbox Pattern', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), multi: signal(true), selectionMode: signal('follow'), }); @@ -452,26 +437,26 @@ describe('Listbox Pattern', () => { options = patterns.options; }); - it('should select an option on navigation (follows focus & multi select)', () => { - expect(listbox.inputs.value()).toEqual(['Apple']); + it('should select an option on navigation', () => { + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(up()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end()); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); listbox.onKeydown(home()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should navigate without selecting an option if the Ctrl key is pressed', () => { - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(home({control: true})); }); @@ -479,47 +464,47 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(space({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana']); }); - it('should select a range of options on Shift + ArrowDown/ArrowUp (follows focus & multi select)', () => { + it('should select a range of options on Shift + ArrowDown/ArrowUp', () => { listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should not allow wrapping while Shift is held down (follows focus & multi select)', () => { + it('should not allow wrapping while Shift is held down', () => { listbox.listBehavior.deselectAll(); listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); - it('should select a range of options on Shift + Space (or Enter) (follows focus & multi select)', () => { + it('should select a range of options on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apricot', 'Banana', 'Blackberry']); + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana', 'Blackberry']); }); - it('should deselect options outside the range on subsequent on Shift + Space (or Enter) (follows focus & multi select)', () => { + it('should deselect options outside the range on subsequent on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.inputs.values()).toEqual(['Banana']); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); listbox.onKeydown(up({control: true})); listbox.onKeydown(up({control: true})); @@ -527,19 +512,19 @@ describe('Listbox Pattern', () => { listbox.onKeydown(up({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); - it('should select the focused option and all options up to the first option on Ctrl + Shift + Home (follows focus & multi select)', () => { + it('should select the focused option and all options up to the first option on Ctrl + Shift + Home', () => { listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); }); - it('should select the focused option and all options down to the last option on Ctrl + Shift + End (follows focus & multi select)', () => { + it('should select the focused option and all options down to the last option on Ctrl + Shift + End', () => { listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); @@ -547,38 +532,43 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Cantaloupe', 'Cherry', 'Clementine', 'Cranberry']); + expect(listbox.inputs.values()).toEqual([ + 'Cantaloupe', + 'Cherry', + 'Clementine', + 'Cranberry', + ]); }); - it('should not be able to change selection when in readonly mode (follows focus & multi select)', () => { + it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignalLike; readonly.set(true); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(space({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should not select disabled options', () => { options[2].disabled.set(true); (listbox.inputs.softDisabled as WritableSignalLike).set(true); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Blackberry']); + expect(listbox.inputs.values()).toEqual(['Blackberry']); }); it('should deselect all except one option on Ctrl + A if all options are selected', () => { listbox.onKeydown(a({control: true})); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); }); @@ -589,63 +579,59 @@ describe('Listbox Pattern', () => { target: options[index].element(), shiftKey: mods?.shift, ctrlKey: mods?.control, - detail: 1, - pointerType: 'mouse', - clientX: 1, - clientY: 1, } as unknown as PointerEvent; } describe('follows focus & single select', () => { - it('should select a single option on click (follows focus & single select)', () => { + it('should select a single option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(false), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); describe('explicit focus & single select', () => { - it('should select an unselected option on click (explicit focus & single select)', () => { + it('should select an unselected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(false), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should deselect a selected option on click (explicit focus & single select)', () => { + it('should deselect a selected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(false), - value: signal(['Apple']), + values: signal(['Apple']), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual([]); }); }); describe('explicit focus & multi select', () => { - it('should select an unselected option on click (explicit focus & multi select)', () => { + it('should select an unselected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should deselect a selected option on click (explicit focus & multi select)', () => { + it('should deselect a selected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), - value: signal(['Apple']), + values: signal(['Apple']), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual([]); }); it('should select options from anchor on shift + click', () => { @@ -653,38 +639,48 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 2)); + listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); - listbox.onClick(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + listbox.onPointerdown(click(options, 5, {shift: true})); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); }); - it('should deselect options outside the range on subsequent shift + clicks (explicit focus & multi select)', () => { + it('should deselect options outside the range on subsequent shift + clicks', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), selectionMode: signal('explicit'), }); - listbox.onClick(click(options, 2)); + listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); - listbox.onClick(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); - listbox.onClick(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + listbox.onPointerdown(click(options, 5, {shift: true})); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); + listbox.onPointerdown(click(options, 0, {shift: true})); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); }); describe('follows focus & multi select', () => { - it('should select a single option on click (follows focus & multi select)', () => { + it('should select a single option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); - listbox.onClick(click(options, 1)); - expect(listbox.inputs.value()).toEqual(['Apricot']); - listbox.onClick(click(options, 2)); - expect(listbox.inputs.value()).toEqual(['Banana']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 1)); + expect(listbox.inputs.values()).toEqual(['Apricot']); + listbox.onPointerdown(click(options, 2)); + expect(listbox.inputs.values()).toEqual(['Banana']); }); it('should select an unselected option on ctrl + click', () => { @@ -692,12 +688,12 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); - listbox.onClick(click(options, 1, {control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); - listbox.onClick(click(options, 2, {control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 1, {control: true})); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); + listbox.onPointerdown(click(options, 2, {control: true})); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); }); it('should deselect a selected option on ctrl + click', () => { @@ -705,10 +701,10 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); - listbox.onClick(click(options, 0, {control: true})); - expect(listbox.inputs.value()).toEqual([]); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0, {control: true})); + expect(listbox.inputs.values()).toEqual([]); }); it('should select a range of options on shift + click', () => { @@ -716,23 +712,33 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 2)); + listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); - listbox.onClick(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + listbox.onPointerdown(click(options, 5, {shift: true})); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); }); - it('should deselect options outside the range on subsequent shift + clicks (follows focus & multi select)', () => { + it('should deselect options outside the range on subsequent shift + clicks', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 2)); + listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); - listbox.onClick(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); - listbox.onClick(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + listbox.onPointerdown(click(options, 5, {shift: true})); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); + listbox.onPointerdown(click(options, 0, {shift: true})); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select a range up to but not including a disabled option on shift + click', () => { @@ -742,12 +748,12 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); options[2].disabled.set(true); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(shift()); - listbox.onClick(click(options, 2, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + listbox.onPointerdown(click(options, 2, {shift: true})); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); expect(listbox.inputs.activeItem()).toEqual(options[2]); }); @@ -758,12 +764,12 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); options[2].disabled.set(true); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); - listbox.onClick(click(options, 2)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 2)); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); @@ -772,12 +778,12 @@ describe('Listbox Pattern', () => { readonly: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); - listbox.onClick(click(options, 1)); - expect(listbox.inputs.value()).toEqual([]); - listbox.onClick(click(options, 2)); - expect(listbox.inputs.value()).toEqual([]); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual([]); + listbox.onPointerdown(click(options, 1)); + expect(listbox.inputs.values()).toEqual([]); + listbox.onPointerdown(click(options, 2)); + expect(listbox.inputs.values()).toEqual([]); }); it('should maintain the range selection between pointer and keyboard', () => { @@ -785,15 +791,15 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 2)); + listbox.onPointerdown(click(options, 2)); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); - listbox.onClick(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + listbox.onPointerdown(click(options, 0, {shift: true})); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select a range from the currently focused option', () => { @@ -801,13 +807,13 @@ describe('Listbox Pattern', () => { multi: signal(true), selectionMode: signal('follow'), }); - listbox.onClick(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + listbox.onPointerdown(click(options, 0)); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); - listbox.onClick(click(options, 4, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry', 'Blueberry']); + listbox.onPointerdown(click(options, 4, {shift: true})); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry', 'Blueberry']); }); }); @@ -829,7 +835,7 @@ describe('Listbox Pattern', () => { it('should set the active index to the first selected option', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Banana']), + values: signal(['Banana']), softDisabled: signal(false), }); listbox.setDefaultState(); @@ -838,7 +844,7 @@ describe('Listbox Pattern', () => { it('should set the active index to the first focusable selected option', () => { const {listbox, options} = getDefaultPatterns({ - value: signal(['Banana', 'Blackberry']), + values: signal(['Banana', 'Blackberry']), softDisabled: signal(false), }); options[2].disabled.set(true); @@ -848,49 +854,12 @@ describe('Listbox Pattern', () => { it('should set the active index to the first option if no selected option is focusable', () => { const {listbox, options} = getDefaultPatterns({ - value: signal(['Banana']), + values: signal(['Banana']), softDisabled: signal(false), }); options[2].disabled.set(true); listbox.setDefaultState(); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); }); - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const {listbox, options} = getDefaultPatterns(); - listbox.inputs.value.set(['Banana']); // Banana is options[2] - listbox.setDefaultStateEffect(); - expect(listbox.inputs.activeItem()).toBe(options[2]); // Should reset to selected Banana - }); - - it('should NOT set default state if keyboard interacted', () => { - const {listbox, options} = getDefaultPatterns(); - listbox.onKeydown(down()); // Interaction (ArrowDown moves to Apricot, options[1]) - - listbox.inputs.value.set(['Banana']); // Banana is options[2] - listbox.setDefaultStateEffect(); - expect(listbox.inputs.activeItem()).toBe(options[1]); // Should stay on Apricot - }); - - it('should NOT set default state if pointer interacted', () => { - const {listbox, options} = getDefaultPatterns(); - const clickEvent = {target: options[1].element()} as any as PointerEvent; - listbox.onClick(clickEvent); // Interaction - - listbox.inputs.value.set(['Banana']); // Banana is options[2] - listbox.setDefaultStateEffect(); - expect(listbox.inputs.activeItem()).toBe(options[1]); // Should stay on Apricot - }); - - it('should NOT set default state if focus-in interacted', () => { - const {listbox, options} = getDefaultPatterns(); - listbox.onFocusIn(); // Interaction - - listbox.inputs.value.set(['Banana']); // Banana is options[2] - listbox.setDefaultStateEffect(); - expect(listbox.inputs.activeItem()).toBe(options[0]); // Should stay on Apple - }); - }); }); }); diff --git a/src/aria/private/listbox/listbox.ts b/src/aria/private/listbox/listbox.ts index c6079f78fbf0..0c927989f2c1 100644 --- a/src/aria/private/listbox/listbox.ts +++ b/src/aria/private/listbox/listbox.ts @@ -7,7 +7,7 @@ */ import {OptionPattern} from './option'; -import {KeyboardEventManager, Modifier, ClickEventManager} from '../behaviors/event-manager'; +import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; import {computed, signal, SignalLike} from '../behaviors/signal-like/signal-like'; import {List, ListInputs} from '../behaviors/list/list'; @@ -22,40 +22,37 @@ export type ListboxInputs = ListInputs, V> & { /** Controls the state of a listbox. */ export class ListboxPattern { - readonly listBehavior: List, V>; - - /** Whether the listbox has been interacted with. */ - readonly hasBeenInteracted = signal(false); + listBehavior: List, V>; /** Whether the list is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; + orientation: SignalLike<'vertical' | 'horizontal'>; /** Whether the listbox is disabled. */ - readonly disabled = computed(() => this.listBehavior.disabled()); + disabled = computed(() => this.listBehavior.disabled()); /** Whether the listbox is readonly. */ - readonly readonly: SignalLike; + readonly: SignalLike; /** The tab index of the listbox. */ - readonly tabIndex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabIndex()); + tabIndex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabIndex()); /** The id of the current active item. */ - readonly activeDescendant = computed(() => this.listBehavior.activeDescendant()); + activeDescendant = computed(() => this.listBehavior.activeDescendant()); /** Whether multiple items in the list can be selected at once. */ multi: SignalLike; /** The number of items in the listbox. */ - readonly setsize = computed(() => this.inputs.items().length); + setsize = computed(() => this.inputs.items().length); /** Whether the listbox selection follows focus. */ - readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow'); + followFocus = computed(() => this.inputs.selectionMode() === 'follow'); /** Whether the listbox should wrap. Used to disable wrapping while range selecting. */ - readonly wrap = signal(true); + wrap = signal(true); /** The key used to navigate to the previous item in the list. */ - readonly prevKey = computed(() => { + prevKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowUp'; } @@ -63,7 +60,7 @@ export class ListboxPattern { }); /** The key used to navigate to the next item in the list. */ - readonly nextKey = computed(() => { + nextKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowDown'; } @@ -71,13 +68,13 @@ export class ListboxPattern { }); /** Represents the space key. Does nothing when the user is actively using typeahead. */ - readonly dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); + dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); /** The regexp used to decide if a key should trigger typeahead. */ - readonly typeaheadRegexp = /^.$/; + typeaheadRegexp = /^.$/; /** The keydown event manager for the listbox. */ - readonly keydown = computed(() => { + keydown = computed(() => { const manager = new KeyboardEventManager(); if (this.readonly()) { @@ -163,9 +160,9 @@ export class ListboxPattern { return manager; }); - /** The click event manager for the listbox. */ - readonly clickManager = computed(() => { - const manager = new ClickEventManager(); + /** The pointerdown event manager for the listbox. */ + pointerdown = computed(() => { + const manager = new PointerEventManager(); if (this.readonly()) { return manager.on(e => this.listBehavior.goto(this._getItem(e)!)); @@ -209,46 +206,28 @@ export class ListboxPattern { validate(): string[] { const violations: string[] = []; - if (!this.inputs.multi() && this.inputs.value().length > 1) { + if (!this.inputs.multi() && this.inputs.values().length > 1) { violations.push( - `A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.value().join(', ')}`, + `A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.values().join(', ')}`, ); } - const values = this.inputs.items().map(o => o.value()); - const duplicates = values.filter((val, idx) => values.indexOf(val) !== idx); - if (duplicates.length > 0) { - violations.push(`Duplicate option value '${duplicates[0]}' detected inside ngListbox.`); - } - - const ids = this.inputs.items().map(o => o.id()); - const duplicateIds = ids.filter((id, idx) => ids.indexOf(id) !== idx); - if (duplicateIds.length > 0) { - violations.push(`Duplicate option ID '${duplicateIds[0]}' detected inside ngListbox.`); - } - return violations; } /** Handles keydown events for the listbox. */ onKeydown(event: KeyboardEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); this.keydown().handle(event); } } - onClick(event: PointerEvent) { + onPointerdown(event: PointerEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); - this.clickManager().handle(event); + this.pointerdown().handle(event); } } - onFocusIn() { - this.hasBeenInteracted.set(true); - } - /** * Sets the listbox to it's default initial state. * @@ -276,21 +255,9 @@ export class ListboxPattern { if (firstItem) { this.inputs.activeItem.set(firstItem); - if (this.followFocus()) { - this.listBehavior.select(); - } } } - /** - * Sets the default active state of the listbox before receiving interaction for the first time. - */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; - - this.setDefaultState(); - } - protected _getItem(e: PointerEvent) { if (!(e.target instanceof HTMLElement)) { return; diff --git a/src/aria/private/listbox/option.ts b/src/aria/private/listbox/option.ts index 0e8de02c2985..69930a3ec06b 100644 --- a/src/aria/private/listbox/option.ts +++ b/src/aria/private/listbox/option.ts @@ -26,37 +26,37 @@ export interface OptionInputs extends Omit, 'index' | 'selectable /** Represents an option in a listbox. */ export class OptionPattern { /** A unique identifier for the option. */ - readonly id: SignalLike; + id: SignalLike; /** The value of the option. */ - readonly value: SignalLike; + value: SignalLike; /** The position of the option in the list. */ - readonly index = computed(() => this.listbox()?.inputs.items().indexOf(this) ?? -1); + index = computed(() => this.listbox()?.inputs.items().indexOf(this) ?? -1); /** Whether the option is active. */ - readonly active = computed(() => this.listbox()?.inputs.activeItem() === this); + active = computed(() => this.listbox()?.inputs.activeItem() === this); /** Whether the option is selected. */ - readonly selected = computed(() => this.listbox()?.inputs.value().includes(this.value())); + selected = computed(() => this.listbox()?.inputs.values().includes(this.value())); /** Whether the option is selectable. */ - readonly selectable = () => true; + selectable = () => true; /** Whether the option is disabled. */ - readonly disabled: SignalLike; + disabled: SignalLike; /** The text used by the typeahead search. */ - readonly searchTerm: SignalLike; + searchTerm: SignalLike; /** A reference to the parent listbox. */ - readonly listbox: SignalLike | undefined>; + listbox: SignalLike | undefined>; /** The tab index of the option. */ - readonly tabIndex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this)); + tabIndex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this)); /** The html element that should receive focus. */ - readonly element: SignalLike; + element: SignalLike; constructor(args: OptionInputs) { this.id = args.id; diff --git a/src/aria/private/menu/menu.spec.ts b/src/aria/private/menu/menu.spec.ts index 026ca1dfff62..5748a8889733 100644 --- a/src/aria/private/menu/menu.spec.ts +++ b/src/aria/private/menu/menu.spec.ts @@ -46,19 +46,6 @@ function getMenuTriggerPattern(opts?: {textDirection: 'ltr' | 'rtl'}) { menu: submenu, disabled: signal(false), }); - - const originalOnClick = trigger.onClick.bind(trigger); - trigger.onClick = () => { - originalOnClick(); - trigger.pendingFocusEffect(); - }; - - const originalOnKeydown = trigger.onKeydown.bind(trigger); - trigger.onKeydown = (event: KeyboardEvent) => { - originalOnKeydown(event); - trigger.pendingFocusEffect(); - }; - return trigger; } @@ -72,7 +59,7 @@ function getMenuBarPattern(values: string[], opts?: {textDirection: 'ltr' | 'rtl textDirection: signal(opts?.textDirection || 'ltr'), multi: signal(false), selectionMode: signal('explicit'), - value: signal([]), + values: signal([]), wrap: signal(true), typeaheadDelay: signal(500), softDisabled: signal(true), @@ -93,7 +80,6 @@ function getMenuBarPattern(values: string[], opts?: {textDirection: 'ltr' | 'rtl parent: signal(menubar), element: signal(element), submenu: signal(undefined), - role: signal('menuitem'), }) as TestMenuItem; }), ); @@ -139,7 +125,6 @@ function getMenuPattern( parent: signal(menu), element: signal(element), submenu: signal(undefined), - role: signal('menuitem'), }) as TestMenuItem; }), ); @@ -325,7 +310,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(true); }); - it('should close a standalone submenu on arrow left', () => { + it('should close submenu on arrow left', () => { menu.onKeydown(right()); expect(submenu.visible()).toBe(true); @@ -359,7 +344,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(false); }); - it('should close a standalone submenu on arrow left after opening via arrow key', () => { + it('should close submenu on arrow left', () => { menu.onKeydown(right()); expect(submenu.visible()).toBe(true); @@ -374,7 +359,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(true); }); - it('should close the standalone menu on selecting an item on click', () => { + it('should close on selecting an item on click', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); expect(submenu.visible()).toBe(true); @@ -382,7 +367,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(false); }); - it('should close the standalone menu on selecting an item on enter', () => { + it('should close on selecting an item on enter', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); expect(submenu.visible()).toBe(true); @@ -390,7 +375,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(false); }); - it('should close the standalone menu on selecting an item on space', () => { + it('should close on selecting an item on space', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); expect(submenu.visible()).toBe(true); @@ -398,7 +383,7 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(false); }); - it('should close the standalone menu on focus out', () => { + it('should close on focus out from the menu', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); expect(submenu.visible()).toBe(true); @@ -458,50 +443,6 @@ describe('Standalone Menu Pattern', () => { expect(submenu.visible()).toBe(false); }); }); - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const items = menu.inputs.items() as TestMenuItem[]; - menu.inputs.activeItem.set(undefined); - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menu.setDefaultStateEffect(); - expect(menu.inputs.activeItem()?.id()).toBe(items[0].id()); // Should reset to item0 because softDisabled is true - }); - - it('should NOT set default state if keyboard interacted', () => { - const items = menu.inputs.items() as TestMenuItem[]; - menu.inputs.activeItem.set(undefined); - menu.onKeydown(down()); // Interaction (ArrowDown moves to item0) - - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menu.setDefaultStateEffect(); - expect(menu.inputs.activeItem()).toBe(items[0]); // Should stay on item0 - }); - - it('should NOT set default state if pointer interacted', () => { - const items = menu.inputs.items() as TestMenuItem[]; - menu.inputs.activeItem.set(undefined); - menu.onMouseOver({target: items[0].element()} as unknown as MouseEvent); // Interaction - - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menu.setDefaultStateEffect(); - expect(menu.inputs.activeItem()).toBe(items[0]); // Should stay on item0 - }); - - it('should NOT set default state if focus-in occurred', () => { - const items = menu.inputs.items() as TestMenuItem[]; - menu.inputs.activeItem.set(undefined); - menu.onFocusIn(); // Interaction - - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menu.setDefaultStateEffect(); - expect(menu.inputs.activeItem()).toBeUndefined(); // Should stay undefined - }); - }); }); describe('Menu Trigger Pattern', () => { @@ -521,22 +462,22 @@ describe('Menu Trigger Pattern', () => { expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[0]); }); - it('should navigate to the first menu item on arrow down on the trigger', () => { + it('should navigate to the first item on arrow down', () => { trigger.onKeydown(down()); expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[0]); }); - it('should navigate to the last menu item on arrow up on the trigger', () => { + it('should navigate to the last item on arrow up', () => { trigger.onKeydown(up()); expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[2]); }); - it('should navigate to the first menu item on enter on the trigger', () => { + it('should navigate to the first item on enter', () => { trigger.onKeydown(enter()); expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[0]); }); - it('should navigate to the first menu item on space on the trigger', () => { + it('should navigate to the first item on space', () => { trigger.onKeydown(space()); expect(menu.inputs.activeItem()).toBe(menu.inputs.items()[0]); }); @@ -594,7 +535,7 @@ describe('Menu Trigger Pattern', () => { expect(submenu?.visible()).toBe(false); }); - it('should close the triggered menu on escape', () => { + it('should close on escape', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); expect(menu.visible()).toBe(true); @@ -604,7 +545,7 @@ describe('Menu Trigger Pattern', () => { expect(menu.visible()).toBe(false); }); - it('should close the triggered menu on selecting an item on click', () => { + it('should close on selecting an item on click', () => { trigger.onClick(); expect(trigger.expanded()).toBe(true); expect(menu.visible()).toBe(true); @@ -621,7 +562,7 @@ describe('Menu Trigger Pattern', () => { expect(submenu?.visible()).toBe(false); }); - it('should close the triggered menu on selecting an item on enter', () => { + it('should close on selecting an item on enter', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); expect(menu.visible()).toBe(true); @@ -636,7 +577,7 @@ describe('Menu Trigger Pattern', () => { expect(submenu?.visible()).toBe(false); }); - it('should close the triggered menu on selecting an item on space', () => { + it('should close on selecting an item on space', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); expect(menu.visible()).toBe(true); @@ -675,7 +616,7 @@ describe('Menu Bar Pattern', () => { }); describe('Navigation', () => { - it('should navigate to the first menubar item on arrow down', () => { + it('should navigate to the first item on arrow down', () => { const menubarItems = menubar.inputs.items(); menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(down()); @@ -684,7 +625,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); - it('should navigate to the last menubar item on arrow up', () => { + it('should navigate to the last item on arrow up', () => { const menubarItems = menubar.inputs.items(); menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(up()); @@ -693,7 +634,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[1]); }); - it('should navigate to the first menubar item on enter', () => { + it('should navigate to the first item on enter', () => { const menubarItems = menubar.inputs.items(); menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(enter()); @@ -702,7 +643,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); - it('should navigate to the first menubar item on space', () => { + it('should navigate to the first item on space', () => { const menubarItems = menubar.inputs.items(); menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(space()); @@ -824,7 +765,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.visible()).toBe(true); }); - it('should close the menubar menu on escape', () => { + it('should close on escape', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menubarItems[0].expanded()).toBe(true); @@ -836,7 +777,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.visible()).toBe(false); }); - it('should close the menubar menu on selecting an item on click', () => { + it('should close on selecting an item on click', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menuA.visible()).toBe(true); @@ -847,7 +788,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.visible()).toBe(false); }); - it('should close the menubar menu on selecting an item on enter', () => { + it('should close on selecting an item on enter', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menuA.visible()).toBe(true); @@ -858,7 +799,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.visible()).toBe(false); }); - it('should close the menubar menu on selecting an item on space', () => { + it('should close on selecting an item on space', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menuA.visible()).toBe(true); @@ -869,7 +810,7 @@ describe('Menu Bar Pattern', () => { expect(menuA.visible()).toBe(false); }); - it('should close the menubar menu on focus out', () => { + it('should close on focus out from the menu', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menuA.visible()).toBe(true); @@ -977,38 +918,5 @@ describe('Menu Bar Pattern', () => { expect(menubar.inputs.activeItem()).toBe(menubarItems[0]); }); }); - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const items = menubar.inputs.items() as TestMenuItem[]; - menubar.inputs.activeItem.set(undefined); - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menubar.setDefaultStateEffect(); - expect(menubar.inputs.activeItem()?.id()).toBe(items[0].id()); // Should reset to item0 because softDisabled is true - }); - - it('should NOT set default state if keyboard interacted', () => { - const items = menubar.inputs.items() as TestMenuItem[]; - menubar.inputs.activeItem.set(undefined); - menubar.onKeydown(down()); // Interaction (ArrowDown moves to item1) - - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menubar.setDefaultStateEffect(); - expect(menubar.inputs.activeItem()).toBeUndefined(); // Should stay undefined - }); - - it('should NOT set default state if focus-in occurred', () => { - const items = menubar.inputs.items() as TestMenuItem[]; - menubar.inputs.activeItem.set(undefined); - menubar.onFocusIn(); // Interaction (stays on item0) - - items[0].inputs.disabled.set(true); - items[1].inputs.disabled.set(true); - menubar.setDefaultStateEffect(); - expect(menubar.inputs.activeItem()).toBeUndefined(); // Should stay undefined - }); - }); }); }); diff --git a/src/aria/private/menu/menu.ts b/src/aria/private/menu/menu.ts index 70657833c7ec..6ad86e70ae2a 100644 --- a/src/aria/private/menu/menu.ts +++ b/src/aria/private/menu/menu.ts @@ -23,7 +23,7 @@ export interface MenuBarInputs extends ListInputs, V> { } /** The inputs for the MenuPattern class. */ -export interface MenuInputs extends Omit, V>, 'value'> { +export interface MenuInputs extends Omit, V>, 'values'> { /** The unique ID of the menu. */ id: SignalLike; @@ -65,38 +65,33 @@ export interface MenuItemInputs extends Omit, 'index' | 'selectab /** A reference to the submenu associated with the menu item. */ submenu: SignalLike | undefined>; - - /** The role of the menu item. */ - role: SignalLike<'menuitem' | 'menuitemradio' | 'menuitemcheckbox'>; } /** The menu ui pattern class. */ export class MenuPattern { /** The unique ID of the menu. */ - readonly id: SignalLike; + id: SignalLike; /** The role of the menu. */ - readonly role = () => 'menu'; + role = () => 'menu'; /** Whether the menu is disabled. */ - readonly disabled = () => this.inputs.disabled(); + disabled = () => this.inputs.disabled(); /** Whether the menu is visible. */ - readonly visible = computed(() => - this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true, - ); + visible = computed(() => (this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true)); /** Controls list behavior for the menu items. */ - readonly listBehavior: List, V>; + listBehavior: List, V>; /** Whether the menu or any of its child elements are currently focused. */ - readonly isFocused = signal(false); + isFocused = signal(false); - /** Whether the menu has received interaction. */ - readonly hasBeenInteracted = signal(false); + /** Whether the menu has received focus. */ + hasBeenFocused = signal(false); /** Whether the menu trigger has been hovered. */ - readonly hasBeenHovered = signal(false); + hasBeenHovered = signal(false); /** Timeout used to open sub-menus on hover. */ _openTimeout: any; @@ -105,10 +100,10 @@ export class MenuPattern { _closeTimeout: any; /** The tab index of the menu. */ - readonly tabIndex = () => this.listBehavior.tabIndex(); + tabIndex = () => this.listBehavior.tabIndex(); /** Whether the menu should be focused on mouse over. */ - readonly shouldFocus = computed(() => { + shouldFocus = computed(() => { const root = this.root(); if (root instanceof MenuTriggerPattern) { @@ -123,46 +118,45 @@ export class MenuPattern { }); /** The key used to expand sub-menus. */ - private readonly _expandKey = computed(() => { + private _expandKey = computed(() => { return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; }); /** The key used to collapse sub-menus. */ - private readonly _collapseKey = computed(() => { + private _collapseKey = computed(() => { return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; }); /** Represents the space key. Does nothing when the user is actively using typeahead. */ - readonly dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); + dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); /** The regexp used to decide if a key should trigger typeahead. */ - readonly typeaheadRegexp = /^.$/; + typeaheadRegexp = /^.$/; /** The root of the menu. */ - readonly root: SignalLike< - MenuTriggerPattern | MenuBarPattern | MenuPattern | undefined - > = computed(() => { - const parent = this.inputs.parent(); + root: SignalLike | MenuBarPattern | MenuPattern | undefined> = + computed(() => { + const parent = this.inputs.parent(); - if (!parent) { - return this; - } + if (!parent) { + return this; + } - if (parent instanceof MenuTriggerPattern) { - return parent; - } + if (parent instanceof MenuTriggerPattern) { + return parent; + } - const grandparent = parent.inputs.parent(); + const grandparent = parent.inputs.parent(); - if (grandparent instanceof MenuBarPattern) { - return grandparent; - } + if (grandparent instanceof MenuBarPattern) { + return grandparent; + } - return grandparent?.root(); - }); + return grandparent?.root(); + }); /** Handles keyboard events for the menu. */ - readonly keydownManager = computed(() => { + keydownManager = computed(() => { return new KeyboardEventManager() .on('ArrowDown', () => this.next(), {ignoreRepeat: false}) .on('ArrowUp', () => this.prev(), {ignoreRepeat: false}) @@ -180,45 +174,19 @@ export class MenuPattern { this.id = inputs.id; this.listBehavior = new List, V>({ ...inputs, - value: signal([]), + values: signal([]), }); } - /** Returns a set of violations */ - validate(): string[] { - const violations: string[] = []; - - const values = this.inputs.items().map(i => i.value()); - const duplicates = values.filter((val, idx) => values.indexOf(val) !== idx); - if (duplicates.length > 0) { - violations.push(`Duplicate value '${duplicates[0]}' detected inside ngMenu.`); - } - - return violations; - } - /** Sets the default state for the menu. */ setDefaultState() { if (!this.inputs.parent()) { - const firstFocusable = this.listBehavior.navigationBehavior.peekFirst(); - if (firstFocusable) { - this.listBehavior.goto(firstFocusable, {focusElement: false}); - } - } - } - - /** Sets the default active state of the menu before receiving interaction for the first time. */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted() || this.hasBeenHovered()) return; - - if (this.inputs.items().length > 0) { - this.setDefaultState(); + this.listBehavior.goto(this.inputs.items()[0], {focusElement: false}); } } /** Handles keyboard events for the menu. */ onKeydown(event: KeyboardEvent) { - this.hasBeenInteracted.set(true); this.keydownManager().handle(event); } @@ -322,7 +290,7 @@ export class MenuPattern { /** Handles focusin events for the menu. */ onFocusIn() { this.isFocused.set(true); - this.hasBeenInteracted.set(true); + this.hasBeenFocused.set(true); } /** Handles the focusout event for the menu. */ @@ -389,21 +357,26 @@ export class MenuPattern { /** Submits the menu. */ submit(item = this.inputs.activeItem()) { - if (!item || item.disabled() || item.submenu()) { - return; - } - const root = this.root(); - if (root instanceof MenuTriggerPattern) { - root.close({refocus: true}); - root?.inputs.menu()?.inputs.itemSelected?.(item.value()); - } else if (root instanceof MenuBarPattern) { - root.close(); - root?.inputs.itemSelected?.(item.value()); - } else if (root instanceof MenuPattern) { - root.inputs.activeItem()?.close({refocus: true}); - root?.inputs.itemSelected?.(item.value()); + if (item && !item.disabled()) { + const isMenu = root instanceof MenuPattern; + const isMenuBar = root instanceof MenuBarPattern; + const isMenuTrigger = root instanceof MenuTriggerPattern; + + if (!item.submenu() && isMenuTrigger) { + root.close({refocus: true}); + } + + if (!item.submenu() && isMenuBar) { + root.close(); + root?.inputs.itemSelected?.(item.value()); + } + + if (!item.submenu() && isMenu) { + root.inputs.activeItem()?.close({refocus: true}); + root?.inputs.itemSelected?.(item.value()); + } } } @@ -479,38 +452,38 @@ export class MenuPattern { /** The menubar ui pattern class. */ export class MenuBarPattern { /** Controls list behavior for the menu items. */ - readonly listBehavior: List, V>; + listBehavior: List, V>; /** The tab index of the menu. */ - readonly tabIndex = () => this.listBehavior.tabIndex(); + tabIndex = () => this.listBehavior.tabIndex(); /** The key used to navigate to the next item. */ - private readonly _nextKey = computed(() => { + private _nextKey = computed(() => { return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; }); /** The key used to navigate to the previous item. */ - private readonly _previousKey = computed(() => { + private _previousKey = computed(() => { return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; }); /** Represents the space key. Does nothing when the user is actively using typeahead. */ - readonly dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); + dynamicSpaceKey = computed(() => (this.listBehavior.isTyping() ? '' : ' ')); /** The regexp used to decide if a key should trigger typeahead. */ - readonly typeaheadRegexp = /^.$/; + typeaheadRegexp = /^.$/; /** Whether the menubar or any of its children are currently focused. */ - readonly isFocused = signal(false); + isFocused = signal(false); - /** Whether the menubar has been interacted with. */ - readonly hasBeenInteracted = signal(false); + /** Whether the menubar has been focused. */ + hasBeenFocused = signal(false); /** Whether the menubar is disabled. */ - readonly disabled = () => this.inputs.disabled(); + disabled = () => this.inputs.disabled(); /** Handles keyboard events for the menu. */ - readonly keydownManager = computed(() => { + keydownManager = computed(() => { return new KeyboardEventManager() .on(this._nextKey, () => this.next(), {ignoreRepeat: false}) .on(this._previousKey, () => this.prev(), {ignoreRepeat: false}) @@ -529,24 +502,11 @@ export class MenuBarPattern { /** Sets the default state for the menubar. */ setDefaultState() { - const firstFocusable = this.listBehavior.navigationBehavior.peekFirst(); - if (firstFocusable) { - this.inputs.activeItem.set(firstFocusable); - } - } - - /** Sets the default active state of the menubar before receiving interaction for the first time. */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; - - if (this.inputs.items().length > 0) { - this.setDefaultState(); - } + this.inputs.activeItem.set(this.inputs.items()[0]); } /** Handles keyboard events for the menu. */ onKeydown(event: KeyboardEvent) { - this.hasBeenInteracted.set(true); this.keydownManager().handle(event); } @@ -574,7 +534,7 @@ export class MenuBarPattern { /** Handles focusin events for the menu bar. */ onFocusIn() { this.isFocused.set(true); - this.hasBeenInteracted.set(true); + this.hasBeenFocused.set(true); } /** Handles focusout events for the menu bar. */ @@ -635,34 +595,29 @@ export class MenuBarPattern { /** The menu trigger ui pattern class. */ export class MenuTriggerPattern { - /** Whether the menu trigger is expanded. */ - readonly expanded = signal(false); + /** Whether the menu is expanded. */ + expanded = signal(false); - /** Whether the menu trigger has received interaction. */ - readonly hasBeenInteracted = signal(false); - - /** The pending focus target when the menu is opened before the menu instance is available. */ - readonly pendingFocus = signal<'first' | 'last' | undefined>(undefined); + /** Whether the menu trigger has received focus. */ + hasBeenFocused = signal(false); /** The role of the menu trigger. */ - readonly role = () => 'button'; + role = () => 'button'; /** Whether the menu trigger has a popup. */ - readonly hasPopup = () => true; + hasPopup = () => true; /** The menu associated with the trigger. */ - readonly menu: SignalLike | undefined>; + menu: SignalLike | undefined>; /** The tab index of the menu trigger. */ - readonly tabIndex = computed(() => - this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0, - ); + tabIndex = computed(() => (this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0)); /** Whether the menu trigger is disabled. */ - readonly disabled = () => this.inputs.disabled(); + disabled = () => this.inputs.disabled(); /** Handles keyboard events for the menu trigger. */ - readonly keydownManager = computed(() => { + keydownManager = computed(() => { return new KeyboardEventManager() .on(' ', () => this.open({first: true})) .on('Enter', () => this.open({first: true})) @@ -675,24 +630,9 @@ export class MenuTriggerPattern { this.menu = this.inputs.menu; } - /** Flushes any pending focus when the menu instance becomes available. */ - pendingFocusEffect(): void { - const menu = this.inputs.menu(); - const intent = this.pendingFocus(); - if (menu && intent) { - if (intent === 'first') { - menu.first(); - } else if (intent === 'last') { - menu.last(); - } - this.pendingFocus.set(undefined); - } - } - /** Handles keyboard events for the menu trigger. */ onKeydown(event: KeyboardEvent) { if (!this.inputs.disabled()) { - this.hasBeenInteracted.set(true); this.keydownManager().handle(event); } } @@ -706,7 +646,7 @@ export class MenuTriggerPattern { /** Handles focusin events for the menu trigger. */ onFocusIn() { - this.hasBeenInteracted.set(true); + this.hasBeenFocused.set(true); } /** Handles focusout events for the menu trigger. */ @@ -728,16 +668,15 @@ export class MenuTriggerPattern { this.expanded.set(true); if (opts?.first) { - this.pendingFocus.set('first'); + this.inputs.menu()?.first(); } else if (opts?.last) { - this.pendingFocus.set('last'); + this.inputs.menu()?.last(); } } /** Closes the menu. */ close(opts: {refocus?: boolean} = {}) { this.expanded.set(false); - this.pendingFocus.set(undefined); this.menu()?.listBehavior.unfocus(); if (opts.refocus) { @@ -758,28 +697,28 @@ export class MenuTriggerPattern { /** The menu item ui pattern class. */ export class MenuItemPattern implements ListItem { /** The value of the menu item. */ - readonly value: SignalLike; + value: SignalLike; /** The unique ID of the menu item. */ - readonly id: SignalLike; + id: SignalLike; /** Whether the menu item is disabled. */ - readonly disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled(); + disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled(); /** The search term for the menu item. */ - readonly searchTerm: SignalLike; + searchTerm: SignalLike; /** The element of the menu item. */ - readonly element: SignalLike; + element: SignalLike; /** Whether the menu item is active. */ - readonly active = computed(() => this.inputs.parent()?.inputs.activeItem() === this); + active = computed(() => this.inputs.parent()?.inputs.activeItem() === this); - /** Whether the menu item has received interaction. */ - readonly hasBeenInteracted = signal(false); + /** Whether the menu item has received focus. */ + hasBeenFocused = signal(false); /** The tab index of the menu item. */ - readonly tabIndex = computed(() => { + tabIndex = computed(() => { if (this.submenu() && this.submenu()?.inputs.activeItem()) { return -1; } @@ -787,28 +726,28 @@ export class MenuItemPattern implements ListItem { }); /** The position of the menu item in the menu. */ - readonly index = computed(() => this.inputs.parent()?.inputs.items().indexOf(this) ?? -1); + index = computed(() => this.inputs.parent()?.inputs.items().indexOf(this) ?? -1); /** Whether the menu item is expanded. */ - readonly expanded = computed(() => (this.submenu() ? this._expanded() : null)); + expanded = computed(() => (this.submenu() ? this._expanded() : null)); /** Whether the menu item is expanded. */ - readonly _expanded = signal(false); + _expanded = signal(false); /** The ID of the menu that the menu item controls. */ - readonly controls = signal(undefined); + controls = signal(undefined); /** The role of the menu item. */ - readonly role = () => this.inputs.role(); + role = () => 'menuitem'; /** Whether the menu item has a popup. */ - readonly hasPopup = computed(() => !!this.submenu()); + hasPopup = computed(() => !!this.submenu()); /** The submenu associated with the menu item. */ - readonly submenu: SignalLike | undefined>; + submenu: SignalLike | undefined>; /** Whether the menu item is selectable. */ - readonly selectable: SignalLike; + selectable: SignalLike; constructor(readonly inputs: MenuItemInputs) { this.id = inputs.id; @@ -861,6 +800,6 @@ export class MenuItemPattern implements ListItem { /** Handles focusin events for the menu item. */ onFocusIn() { - this.hasBeenInteracted.set(true); + this.hasBeenFocused.set(true); } } diff --git a/src/aria/private/public-api.ts b/src/aria/private/public-api.ts index 213e6706b137..ed8716c7b67b 100644 --- a/src/aria/private/public-api.ts +++ b/src/aria/private/public-api.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ +export * from './combobox/combobox'; export * from './listbox/listbox'; export * from './listbox/option'; +export * from './listbox/combobox-listbox'; export * from './menu/menu'; export * from './behaviors/signal-like/signal-like'; export * from './tabs/tabs'; @@ -17,14 +19,9 @@ export * from './toolbar/toolbar-widget-group'; export * from './accordion/accordion'; export * from './toolbar/toolbar'; export * from './tree/tree'; +export * from './tree/combobox-tree'; export * from './grid/grid'; export * from './grid/row'; export * from './grid/cell'; export * from './grid/widget'; export * from './deferred-content'; -export * from './utils/collection'; -export * from './utils/element'; -export * from './utils/element-resolver'; -export * from './utils/transforms'; -export * from './utils/violations'; -export * from './combobox/combobox'; diff --git a/src/aria/private/tabs/tabs.spec.ts b/src/aria/private/tabs/tabs.spec.ts index 196b80dcaf3b..71129712e423 100644 --- a/src/aria/private/tabs/tabs.spec.ts +++ b/src/aria/private/tabs/tabs.spec.ts @@ -65,32 +65,37 @@ describe('Tabs Pattern', () => { softDisabled: signal(true), items: signal([]), element: signal(document.createElement('div')), - selectedTab: signal(undefined), }; tabListPattern = new TabListPattern(tabListInputs); // Initiate a list of TabPatterns. tabInputs = [ { - tabList: signal(tabListPattern), - tabPanel: signal(undefined), + tablist: signal(tabListPattern), + tabpanel: signal(undefined), id: signal('tab-1-id'), element: signal(createTabElement()), disabled: signal(false), + value: signal('tab-1'), + expanded: signal(false), }, { - tabList: signal(tabListPattern), - tabPanel: signal(undefined), + tablist: signal(tabListPattern), + tabpanel: signal(undefined), id: signal('tab-2-id'), element: signal(createTabElement()), disabled: signal(false), + value: signal('tab-2'), + expanded: signal(false), }, { - tabList: signal(tabListPattern), - tabPanel: signal(undefined), + tablist: signal(tabListPattern), + tabpanel: signal(undefined), id: signal('tab-3-id'), element: signal(createTabElement()), disabled: signal(false), + value: signal('tab-3'), + expanded: signal(false), }, ]; tabPatterns = [ @@ -104,14 +109,17 @@ describe('Tabs Pattern', () => { { id: signal('tabpanel-1-id'), tab: signal(undefined), + value: signal('tab-1'), }, { id: signal('tabpanel-2-id'), tab: signal(undefined), + value: signal('tab-2'), }, { id: signal('tabpanel-3-id'), tab: signal(undefined), + value: signal('tab-3'), }, ]; tabPanelPatterns = [ @@ -121,9 +129,9 @@ describe('Tabs Pattern', () => { ]; // Binding between tabs and tabpanels. - tabInputs[0].tabPanel.set(tabPanelPatterns[0]); - tabInputs[1].tabPanel.set(tabPanelPatterns[1]); - tabInputs[2].tabPanel.set(tabPanelPatterns[2]); + tabInputs[0].tabpanel.set(tabPanelPatterns[0]); + tabInputs[1].tabpanel.set(tabPanelPatterns[1]); + tabInputs[2].tabpanel.set(tabPanelPatterns[2]); tabPanelInputs[0].tab.set(tabPatterns[0]); tabPanelInputs[1].tab.set(tabPatterns[1]); tabPanelInputs[2].tab.set(tabPatterns[2]); @@ -135,8 +143,8 @@ describe('Tabs Pattern', () => { describe('#open', () => { it('should open a tab with value', () => { expect(tabListPattern.selectedTab()).toBeUndefined(); - tabListPattern.open(tabPatterns[0]); - expect(tabListPattern.selectedTab()!).toBe(tabPatterns[0]); + tabListPattern.open('tab-1'); + expect(tabListPattern.selectedTab()!.value()).toBe('tab-1'); }); it('should open a tab with tab pattern instance', () => { @@ -192,43 +200,6 @@ describe('Tabs Pattern', () => { tabListPattern.setDefaultState(); expect(tabListInputs.activeItem()).toBe(tabPatterns[0]); }); - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - tabListPattern.selectedTab.set(tabPatterns[2]); - tabListPattern.setDefaultStateEffect(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[2]); // Should reset to selected tab3 - }); - - it('should NOT set default state if keyboard interacted', () => { - tabListInputs.activeItem.set(tabPatterns[0]); - tabListPattern.onKeydown(right()); // Interaction (ArrowRight moves to tab2) - - tabListPattern.selectedTab.set(tabPatterns[2]); - tabListPattern.setDefaultStateEffect(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[1]); // Should stay on tab2 - }); - - it('should NOT set default state if focus-in occurred', () => { - tabListInputs.activeItem.set(tabPatterns[0]); - tabListPattern.onFocusIn(); // Interaction - - tabListPattern.selectedTab.set(tabPatterns[2]); - tabListPattern.setDefaultStateEffect(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[0]); // Should stay on tab1 - }); - - it('should NOT set default state if pointer interacted', () => { - tabListInputs.activeItem.set(tabPatterns[0]); - tabListPattern.onClick({ - target: tabPatterns[1].element(), - } as unknown as PointerEvent); // Interaction - - tabListPattern.selectedTab.set(tabPatterns[2]); - tabListPattern.setDefaultStateEffect(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[1]); // Should stay on tab2 - }); - }); }); describe('Keyboard Navigation', () => { @@ -420,21 +391,4 @@ describe('Tabs Pattern', () => { expect(tabPanelPatterns[2].labelledBy()).toBe('tab-3-id'); }); }); - - describe('ActiveDescendant mode', () => { - beforeEach(() => { - tabListInputs.focusMode.set('activedescendant'); - tabListPattern.setDefaultState(); - }); - - it('should update activeDescendant when navigating', () => { - expect(tabListPattern.activeDescendant()).toBe('tab-1-id'); - - tabListPattern.onKeydown(right()); - expect(tabListPattern.activeDescendant()).toBe('tab-2-id'); - - tabListPattern.onKeydown(right()); - expect(tabListPattern.activeDescendant()).toBe('tab-3-id'); - }); - }); }); diff --git a/src/aria/private/tabs/tabs.ts b/src/aria/private/tabs/tabs.ts index 91e009e53621..044b3f68370a 100644 --- a/src/aria/private/tabs/tabs.ts +++ b/src/aria/private/tabs/tabs.ts @@ -6,39 +6,48 @@ * found in the LICENSE file at https://angular.dev/license */ -import {KeyboardEventManager, ClickEventManager} from '../behaviors/event-manager'; -import {ExpansionItem, ListExpansion, ListExpansionInputs} from '../behaviors/expansion/expansion'; +import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; +import {ExpansionItem, ListExpansionInputs, ListExpansion} from '../behaviors/expansion/expansion'; import { SignalLike, - WritableSignalLike, computed, - linkedSignal, signal, + WritableSignalLike, } from '../behaviors/signal-like/signal-like'; +import {LabelControl, LabelControlOptionalInputs} from '../behaviors/label/label'; import {ListFocus} from '../behaviors/list-focus/list-focus'; import { + ListNavigationItem, ListNavigation, ListNavigationInputs, - ListNavigationItem, } from '../behaviors/list-navigation/list-navigation'; /** The required inputs to tabs. */ export interface TabInputs - extends Omit, Omit { + extends Omit, Omit { /** The parent tablist that controls the tab. */ - tabList: SignalLike; + tablist: SignalLike; /** The remote tabpanel controlled by the tab. */ - tabPanel: SignalLike; + tabpanel: SignalLike; + + /** The remote tabpanel unique identifier. */ + value: SignalLike; } /** A tab in a tablist. */ export class TabPattern { /** A global unique identifier for the tab. */ - readonly id: SignalLike; // set from inputs + readonly id: SignalLike = () => this.inputs.id(); + + /** The index of the tab. */ + readonly index = computed(() => this.inputs.tablist().inputs.items().indexOf(this)); + + /** The remote tabpanel unique identifier. */ + readonly value: SignalLike = () => this.inputs.value(); /** Whether the tab is disabled. */ - readonly disabled: SignalLike; // set from inputs + readonly disabled: SignalLike = () => this.inputs.disabled(); /** The html element that should receive focus. */ readonly element: SignalLike = () => this.inputs.element()!; @@ -46,52 +55,53 @@ export class TabPattern { /** Whether this tab has expandable panel. */ readonly expandable: SignalLike = () => true; - /* - * Whether the tab panel is expanded. - * Primarily controlled by the behavior, which will read/write this value. - * The consumer of this pattern will instead only use the selectedTab input. - * The pattern will be responsible for synchronizing their state. - */ - readonly expanded: WritableSignalLike = linkedSignal( - () => this.inputs.tabList().selectedTab() === this, - ); + /** Whether the tab panel is expanded. */ + readonly expanded: WritableSignalLike; /** Whether the tab is active. */ - readonly active = computed(() => this.inputs.tabList().inputs.activeItem() === this); + readonly active = computed(() => this.inputs.tablist().inputs.activeItem() === this); /** Whether the tab is selected. */ - readonly selected = computed(() => this.inputs.tabList().selectedTab() === this); + readonly selected = computed(() => this.inputs.tablist().selectedTab() === this); /** The tab index of the tab. */ - readonly tabIndex = computed(() => this.inputs.tabList().focusBehavior.getItemTabIndex(this)); + readonly tabIndex = computed(() => this.inputs.tablist().focusBehavior.getItemTabIndex(this)); /** The id of the tabpanel associated with the tab. */ - readonly controls = computed(() => this.inputs.tabPanel()?.id()); + readonly controls = computed(() => this.inputs.tabpanel()?.id()); constructor(readonly inputs: TabInputs) { - this.id = inputs.id; - this.disabled = inputs.disabled; + this.expanded = inputs.expanded; } /** Opens the tab. */ open(): boolean { - return this.inputs.tabList().open(this); + return this.inputs.tablist().open(this); } } /** The required inputs for the tabpanel. */ -export interface TabPanelInputs { +export interface TabPanelInputs extends LabelControlOptionalInputs { /** A global unique identifier for the tabpanel. */ id: SignalLike; /** The tab that controls this tabpanel. */ - readonly tab: SignalLike; + tab: SignalLike; + + /** A local unique identifier for the tabpanel. */ + value: SignalLike; } /** A tabpanel associated with a tab. */ export class TabPanelPattern { /** A global unique identifier for the tabpanel. */ - readonly id: SignalLike; // set from inputs + readonly id: SignalLike = () => this.inputs.id(); + + /** A local unique identifier for the tabpanel. */ + readonly value: SignalLike = () => this.inputs.value(); + + /** Controls label for this tabpanel. */ + readonly labelManager: LabelControl; /** Whether the tabpanel is hidden. */ readonly hidden = computed(() => this.inputs.tab()?.expanded() === false); @@ -100,10 +110,17 @@ export class TabPanelPattern { readonly tabIndex = computed(() => (this.hidden() ? -1 : 0)); /** The aria-labelledby value for this tabpanel. */ - readonly labelledBy = computed(() => this.inputs.tab()?.id()); + readonly labelledBy = computed(() => + this.labelManager.labelledBy().length > 0 + ? this.labelManager.labelledBy().join(' ') + : undefined, + ); constructor(readonly inputs: TabPanelInputs) { - this.id = inputs.id; + this.labelManager = new LabelControl({ + ...inputs, + defaultLabelledBy: computed(() => (this.inputs.tab() ? [this.inputs.tab()!.id()] : [])), + }); } } @@ -114,9 +131,6 @@ export interface TabListInputs Omit { /** The selection strategy used by the tablist. */ selectionMode: SignalLike<'follow' | 'explicit'>; - - /** The currently selected tab. */ - selectedTab: WritableSignalLike; } /** Controls the state of a tablist. */ @@ -130,20 +144,17 @@ export class TabListPattern { /** Controls expansion for the tablist. */ readonly expansionBehavior: ListExpansion; - /** Whether the tablist has been interacted with. */ - readonly hasBeenInteracted = signal(false); - /** The currently active tab. */ - readonly activeTab: SignalLike; // set from inputs + readonly activeTab: SignalLike = () => this.inputs.activeItem(); /** The currently selected tab. */ - readonly selectedTab: WritableSignalLike; // set from inputs + readonly selectedTab: WritableSignalLike = signal(undefined); /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; // set from inputs + readonly orientation: SignalLike<'vertical' | 'horizontal'> = () => this.inputs.orientation(); /** Whether the tablist is disabled. */ - readonly disabled: SignalLike; // set from inputs + readonly disabled: SignalLike = () => this.inputs.disabled(); /** The tab index of the tablist. */ readonly tabIndex = computed(() => this.focusBehavior.getListTabIndex()); @@ -189,19 +200,14 @@ export class TabListPattern { .on('Enter', () => this.open()); }); - /** The click event manager for the tablist. */ - readonly clickManager = computed(() => { - return new ClickEventManager().on(e => + /** The pointerdown event manager for the tablist. */ + readonly pointerdown = computed(() => { + return new PointerEventManager().on(e => this._navigate(() => this.navigationBehavior.goto(this._getItem(e)!), true), ); }); constructor(readonly inputs: TabListInputs) { - this.selectedTab = inputs.selectedTab; - this.activeTab = inputs.activeItem; - this.orientation = inputs.orientation; - this.disabled = inputs.disabled; - this.focusBehavior = new ListFocus(inputs); this.navigationBehavior = new ListNavigation({ @@ -243,39 +249,33 @@ export class TabListPattern { } } - /** Sets the default active state of the tablist before receiving interaction for the first time. */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; - - this.setDefaultState(); - } - /** Handles keydown events for the tablist. */ onKeydown(event: KeyboardEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); this.keydown().handle(event); } } - /** The click event manager for the tablist. */ - onClick(event: PointerEvent) { + /** The pointerdown event manager for the tablist. */ + onPointerdown(event: PointerEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); - this.clickManager().handle(event); + this.pointerdown().handle(event); } } - /** Handles focusin events for the tablist. */ - onFocusIn() { - this.hasBeenInteracted.set(true); - } + /** Opens the tab by given value. */ + open(value: string): boolean; /** Opens the given tab or the current active tab. */ open(tab?: TabPattern): boolean; - open(tab: TabPattern | undefined): boolean { + + open(tab: TabPattern | string | undefined): boolean { tab ??= this.activeTab(); + if (typeof tab === 'string') { + tab = this.inputs.items().find(t => t.value() === tab); + } + if (tab === undefined) return false; const success = this.expansionBehavior.open(tab); diff --git a/src/aria/private/testing/BUILD.bazel b/src/aria/private/testing/BUILD.bazel deleted file mode 100644 index 809d7d9386dc..000000000000 --- a/src/aria/private/testing/BUILD.bazel +++ /dev/null @@ -1,10 +0,0 @@ -load("//tools:defaults.bzl", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - testonly = True, - srcs = ["test-helpers.ts"], - deps = [], -) diff --git a/src/aria/private/testing/test-helpers.ts b/src/aria/private/testing/test-helpers.ts deleted file mode 100644 index 5d31ec0b50fe..000000000000 --- a/src/aria/private/testing/test-helpers.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -/** - * Yields execution to the microtask queue, allowing MutationObserver callbacks to fire. - * Useful for testing async behaviors without relying on Zone.js. - */ -export function waitForMicrotasks(): Promise { - return new Promise(resolve => setTimeout(resolve, 0)); -} diff --git a/src/aria/private/toolbar/toolbar-widget.ts b/src/aria/private/toolbar/toolbar-widget.ts index 4239de8b2727..28cfbc9edfd4 100644 --- a/src/aria/private/toolbar/toolbar-widget.ts +++ b/src/aria/private/toolbar/toolbar-widget.ts @@ -56,7 +56,7 @@ export class ToolbarWidgetPattern implements ListItem { /** Whether the widget is selected (only relevant in a selection group). */ readonly selected = computed(() => - this.toolbar().listBehavior.inputs.value().includes(this.value()), + this.toolbar().listBehavior.inputs.values().includes(this.value()), ); /** Whether the widget is currently the active one (focused). */ diff --git a/src/aria/private/toolbar/toolbar.spec.ts b/src/aria/private/toolbar/toolbar.spec.ts index b62ec5d01fbc..318ec4bb3af5 100644 --- a/src/aria/private/toolbar/toolbar.spec.ts +++ b/src/aria/private/toolbar/toolbar.spec.ts @@ -51,7 +51,6 @@ function clickItem(item: ToolbarWidgetPattern, mods?: ModifierKeys) { target: item.element(), shiftKey: mods?.shift, ctrlKey: mods?.control, - preventDefault: () => {}, // Added to prevent TypeError } as unknown as PointerEvent; } @@ -80,7 +79,7 @@ function getToolbarPattern( element, items, activeItem, - value: signal([]), + values: signal([]), wrap: signal(inputs.wrap ?? true), disabled: signal(inputs.disabled ?? false), softDisabled: signal(inputs.softDisabled ?? true), @@ -163,23 +162,9 @@ describe('Toolbar Pattern', () => { return toolbar.inputs.items().find(item => item.value() === value)!; } - describe('Tabindex', () => { - it('should expose tabIndex signals', () => { - const {toolbar, items} = getPatterns(); - - expect(items[0].tabIndex()).toBe(0); - expect(items[1].tabIndex()).toBe(-1); - - toolbar.onKeydown(right()); // Move focus to item 1 - - expect(items[0].tabIndex()).toBe(-1); - expect(items[1].tabIndex()).toBe(0); - }); - }); - describe('Navigation', () => { describe('with horizontal orientation', () => { - it('should navigate on click (horizontal, ltr)', () => { + it('should navigate on click', () => { const {toolbar} = getPatterns(); const item5 = getItem(toolbar, 'item 5'); toolbar.onClick(clickItem(item5)); @@ -200,20 +185,20 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should not navigate next on ArrowDown when not in a widget group (ltr)', () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(down()); // Item 0 -> Item 0 expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should not navigate prev on ArrowUp when not in a widget group (ltr)', () => { + it('should not navigate prev on ArrowUp when not in a widget group', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(right()); // Item 0 -> Item 1 toolbar.onKeydown(up()); // Item 1 -> Item 1 expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate next in a widget group on ArrowDown (ltr)', () => { + it('should navigate next in a widget group on ArrowDown', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(right()); // Item 0 -> Item 1 @@ -223,7 +208,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 3'); }); - it('should navigate prev in a widget group on ArrowUp (ltr)', () => { + it('should navigate prev in a widget group on ArrowUp', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(right()); // Item 0 -> Item 1 @@ -234,7 +219,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should navigate last to first in a widget group on ArrowDown (ltr)', () => { + it('should navigate last to first in a widget group on ArrowDown', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(right()); // Item 0 -> Item 1 @@ -246,7 +231,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should navigate first to last in a widget group on ArrowUp (ltr)', () => { + it('should navigate first to last in a widget group on ArrowUp', () => { const {toolbar} = getPatterns(); toolbar.onKeydown(right()); // Item 0 -> Item 1 @@ -257,14 +242,14 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first (horizontal, ltr)', () => { + it('should not wrap from last to first', () => { const {toolbar} = getPatterns({wrap: false}); toolbar.onKeydown(end()); toolbar.onKeydown(right()); expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last (horizontal, ltr)', () => { + it('should not wrap from first to last', () => { const {toolbar} = getPatterns({wrap: false}); toolbar.onKeydown(left()); expect(toolbar.activeItem()?.value()).toBe('item 0'); @@ -272,7 +257,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled true', () => { - it('should not skip disabled items when navigating next (horizontal, ltr)', () => { + it('should not skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: true}); items[1].inputs.disabled.set(true); @@ -280,7 +265,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should not skip disabled items when navigating prev (horizontal, ltr)', () => { + it('should not skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: true}); items[1].inputs.disabled.set(true); @@ -307,7 +292,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should navigate to the last item on End (horizontal, ltr)', () => { + it('should navigate to the last item on End', () => { const {toolbar, items} = getPatterns({softDisabled: true}); items[items.length - 1].inputs.disabled.set(true); @@ -315,7 +300,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should navigate to the first item on Home (horizontal, ltr)', () => { + it('should navigate to the first item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: true}); items[0].inputs.disabled.set(true); @@ -326,7 +311,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first (horizontal, ltr)', () => { + it('should wrap from last to first', () => { const {toolbar, items} = getPatterns({softDisabled: true, wrap: true}); items[0].inputs.disabled.set(true); @@ -336,7 +321,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should wrap from first to last (horizontal, ltr)', () => { + it('should wrap from first to last', () => { const {toolbar, items} = getPatterns({softDisabled: true, wrap: true}); items[items.length - 1].inputs.disabled.set(true); @@ -355,7 +340,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should skip disabled items when navigating next (horizontal, ltr)', () => { + it('should skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: false}); items[1].inputs.disabled.set(true); @@ -363,7 +348,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should skip disabled items when navigating prev (horizontal, ltr)', () => { + it('should skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: false}); items[1].inputs.disabled.set(true); @@ -402,7 +387,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate to the last focusable item on End (horizontal, ltr)', () => { + it('should navigate to the last focusable item on End', () => { const {toolbar, items} = getPatterns({softDisabled: false}); items[items.length - 1].inputs.disabled.set(true); @@ -410,7 +395,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 7'); }); - it('should navigate to the first focusable item on Home (horizontal, ltr)', () => { + it('should navigate to the first focusable item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: false}); items[0].inputs.disabled.set(true); @@ -421,7 +406,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first focusable item (horizontal, ltr)', () => { + it('should wrap from last to first focusable item', () => { const {toolbar, items} = getPatterns({softDisabled: false, wrap: true}); items[0].inputs.disabled.set(true); @@ -431,7 +416,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should wrap from first to last focusable item (horizontal, ltr)', () => { + it('should wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({softDisabled: false, wrap: true}); items[items.length - 1].inputs.disabled.set(true); @@ -443,7 +428,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first focusable item (horizontal, ltr)', () => { + it('should not wrap from last to first focusable item', () => { const {toolbar, items} = getPatterns({softDisabled: false, wrap: false}); items[items.length - 1].inputs.disabled.set(true); toolbar.onKeydown(end()); @@ -452,7 +437,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 7'); }); - it('should not wrap from first to last focusable item (horizontal, ltr)', () => { + it('should not wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({softDisabled: false, wrap: false}); items[0].inputs.disabled.set(true); @@ -467,40 +452,40 @@ describe('Toolbar Pattern', () => { }); describe('with rtl text direction', () => { - it('should navigate on click (horizontal, rtl)', () => { + it('should navigate on click', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); const item5 = getItem(toolbar, 'item 5'); toolbar.onClick(clickItem(item5)); expect(toolbar.activeItem()?.value()).toBe('item 5'); }); - it('should navigate next on ArrowLeft (rtl)', () => { + it('should navigate next on ArrowLeft', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate prev on ArrowRight (rtl)', () => { + it('should navigate prev on ArrowRight', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 toolbar.onKeydown(right()); // Item 1 -> Item 0 expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should not navigate next on ArrowDown when not in a widget group (rtl)', () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(up()); // Item 0 -> Item 0 expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should not navigate prev on ArrowUp when not in a widget group (rtl)', () => { + it('should not navigate prev on ArrowUp when not in a widget group', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 toolbar.onKeydown(down()); // Item 1 -> Item 1 expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate next in a widget group on ArrowDown (rtl)', () => { + it('should navigate next in a widget group on ArrowDown', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 @@ -510,7 +495,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 3'); }); - it('should navigate prev in a widget group on ArrowUp (rtl)', () => { + it('should navigate prev in a widget group on ArrowUp', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 @@ -521,7 +506,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should navigate first to last in a widget group on ArrowUp (rtl)', () => { + it('should navigate first to last in a widget group on ArrowUp', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 @@ -531,7 +516,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 4'); }); - it('should navigate last to first in a widget group on ArrowDown (rtl)', () => { + it('should navigate last to first in a widget group on ArrowDown', () => { const {toolbar} = getPatterns({textDirection: 'rtl'}); toolbar.onKeydown(left()); // Item 0 -> Item 1 @@ -544,7 +529,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled true', () => { - it('should not skip disabled items when navigating next (horizontal, rtl)', () => { + it('should not skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); items[1].inputs.disabled.set(true); @@ -552,7 +537,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should not skip disabled items when navigating prev (horizontal, rtl)', () => { + it('should not skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); items[1].inputs.disabled.set(true); @@ -563,7 +548,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate to the last item on End (horizontal, rtl)', () => { + it('should navigate to the last item on End', () => { const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); items[items.length - 1].inputs.disabled.set(true); @@ -571,7 +556,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should navigate to the first item on Home (horizontal, rtl)', () => { + it('should navigate to the first item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); items[0].inputs.disabled.set(true); @@ -582,7 +567,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first (horizontal, rtl)', () => { + it('should wrap from last to first', () => { const {toolbar, items} = getPatterns({ wrap: true, softDisabled: true, @@ -596,7 +581,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should wrap from first to last (horizontal, rtl)', () => { + it('should wrap from first to last', () => { const {toolbar, items} = getPatterns({ wrap: true, softDisabled: true, @@ -610,7 +595,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first (horizontal, rtl)', () => { + it('should not wrap from last to first', () => { const {toolbar} = getPatterns({ wrap: false, softDisabled: true, @@ -622,7 +607,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last (horizontal, rtl)', () => { + it('should not wrap from first to last', () => { const {toolbar} = getPatterns({ wrap: false, softDisabled: true, @@ -637,7 +622,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled false', () => { - it('should skip disabled items when navigating next (horizontal, rtl)', () => { + it('should skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); items[1].inputs.disabled.set(true); @@ -645,7 +630,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should skip disabled items when navigating prev (horizontal, rtl)', () => { + it('should skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); items[1].inputs.disabled.set(true); @@ -655,7 +640,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should navigate to the last focusable item on End (horizontal, rtl)', () => { + it('should navigate to the last focusable item on End', () => { const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); items[items.length - 1].inputs.disabled.set(true); @@ -663,7 +648,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 7'); }); - it('should navigate to the first focusable item on Home (horizontal, rtl)', () => { + it('should navigate to the first focusable item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); items[0].inputs.disabled.set(true); @@ -674,7 +659,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first focusable item (horizontal, rtl)', () => { + it('should wrap from last to first focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: true, @@ -688,7 +673,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should wrap from first to last focusable item (horizontal, rtl)', () => { + it('should wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: true, @@ -704,7 +689,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first focusable item (horizontal, rtl)', () => { + it('should not wrap from last to first focusable item', () => { const {toolbar} = getPatterns({ softDisabled: false, wrap: false, @@ -715,7 +700,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last focusable item (horizontal, rtl)', () => { + it('should not wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: false, @@ -735,13 +720,13 @@ describe('Toolbar Pattern', () => { describe('with vertical orientation', () => { describe('with ltr text direction', () => { - it('should navigate next on ArrowDown (vertical, ltr)', () => { + it('should navigate next on ArrowDown', () => { const {toolbar} = getPatterns({orientation: 'vertical'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate prev on ArrowUp (vertical, ltr)', () => { + it('should navigate prev on ArrowUp', () => { const {toolbar} = getPatterns({orientation: 'vertical'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 toolbar.onKeydown(up()); // Item 1 -> Item 0 @@ -805,7 +790,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled true', () => { - it('should not skip disabled items when navigating next (vertical, ltr)', () => { + it('should not skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); items[1].inputs.disabled.set(true); @@ -814,7 +799,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should not skip disabled items when navigating prev (vertical, ltr)', () => { + it('should not skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); items[1].inputs.disabled.set(true); @@ -825,7 +810,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate to the last item on End (vertical, ltr)', () => { + it('should navigate to the last item on End', () => { const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); items[items.length - 1].inputs.disabled.set(true); @@ -833,7 +818,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should navigate to the first item on Home (vertical, ltr)', () => { + it('should navigate to the first item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); items[0].inputs.disabled.set(true); @@ -844,7 +829,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first (vertical, ltr)', () => { + it('should wrap from last to first', () => { const {toolbar, items} = getPatterns({ softDisabled: true, wrap: true, @@ -858,7 +843,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should wrap from first to last (vertical, ltr)', () => { + it('should wrap from first to last', () => { const {toolbar, items} = getPatterns({ softDisabled: true, wrap: true, @@ -874,7 +859,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first (vertical, ltr)', () => { + it('should not wrap from last to first', () => { const {toolbar} = getPatterns({ softDisabled: true, wrap: false, @@ -886,7 +871,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last (vertical, ltr)', () => { + it('should not wrap from first to last', () => { const {toolbar} = getPatterns({ softDisabled: true, wrap: false, @@ -901,7 +886,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled false', () => { - it('should skip disabled items when navigating next (vertical, ltr)', () => { + it('should skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); items[1].inputs.disabled.set(true); @@ -910,7 +895,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()).toBe(items[2]); }); - it('should skip disabled items when navigating prev (vertical, ltr)', () => { + it('should skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); items[1].inputs.disabled.set(true); @@ -920,7 +905,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should navigate to the last focusable item on End (vertical, ltr)', () => { + it('should navigate to the last focusable item on End', () => { const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); items[items.length - 1].inputs.disabled.set(true); @@ -928,7 +913,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 7'); }); - it('should navigate to the first focusable item on Home (vertical, ltr)', () => { + it('should navigate to the first focusable item on Home', () => { const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); items[0].inputs.disabled.set(true); @@ -939,7 +924,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first focusable item (vertical, ltr)', () => { + it('should wrap from last to first focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: true, @@ -953,7 +938,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should wrap from first to last focusable item (vertical, ltr)', () => { + it('should wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: true, @@ -969,7 +954,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first focusable item (vertical, ltr)', () => { + it('should not wrap from last to first focusable item', () => { const {toolbar} = getPatterns({ softDisabled: false, wrap: false, @@ -981,7 +966,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last focusable item (vertical, ltr)', () => { + it('should not wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ softDisabled: false, wrap: false, @@ -999,20 +984,20 @@ describe('Toolbar Pattern', () => { }); describe('with rtl text direction', () => { - it('should navigate next on ArrowDown (vertical, rtl)', () => { + it('should navigate next on ArrowDown', () => { const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate prev on ArrowUp (vertical, rtl)', () => { + it('should navigate prev on ArrowUp', () => { const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 toolbar.onKeydown(up()); // Item 1 -> Item 0 expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should navigate last to first in a widget group on ArrowLeft (vertical, rtl)', () => { + it('should navigate last to first in a widget group on ArrowLeft', () => { const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 @@ -1024,7 +1009,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 2'); }); - it('should navigate first to last in a widget group on ArrowRight (vertical, rtl)', () => { + it('should navigate first to last in a widget group on ArrowRight', () => { const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); toolbar.onKeydown(down()); // Item 0 -> Item 1 @@ -1035,7 +1020,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled true', () => { - it('should not skip disabled items when navigating next (vertical, rtl)', () => { + it('should not skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({ softDisabled: true, orientation: 'vertical', @@ -1047,7 +1032,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should not skip disabled items when navigating prev (vertical, rtl)', () => { + it('should not skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({ softDisabled: true, orientation: 'vertical', @@ -1062,7 +1047,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should navigate to the last item on End (vertical, rtl)', () => { + it('should navigate to the last item on End', () => { const {toolbar, items} = getPatterns({ softDisabled: true, orientation: 'vertical', @@ -1075,7 +1060,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should navigate to the first item on Home (vertical, rtl)', () => { + it('should navigate to the first item on Home', () => { const {toolbar, items} = getPatterns({ softDisabled: true, orientation: 'vertical', @@ -1090,7 +1075,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first (vertical, rtl)', () => { + it('should wrap from last to first', () => { const {toolbar, items} = getPatterns({ softDisabled: true, wrap: true, @@ -1105,7 +1090,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should wrap from first to last (vertical, rtl)', () => { + it('should wrap from first to last', () => { const {toolbar, items} = getPatterns({ softDisabled: true, wrap: true, @@ -1122,7 +1107,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first (vertical, rtl)', () => { + it('should not wrap from last to first', () => { const {toolbar} = getPatterns({ softDisabled: true, wrap: false, @@ -1135,7 +1120,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last (vertical, rtl)', () => { + it('should not wrap from first to last', () => { const {toolbar} = getPatterns({ softDisabled: true, wrap: false, @@ -1151,7 +1136,7 @@ describe('Toolbar Pattern', () => { }); describe('with softDisabled false', () => { - it('should skip disabled items when navigating next (vertical, rtl)', () => { + it('should skip disabled items when navigating next', () => { const {toolbar, items} = getPatterns({ softDisabled: false, orientation: 'vertical', @@ -1164,7 +1149,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()).toBe(items[2]); }); - it('should skip disabled items when navigating prev (vertical, rtl)', () => { + it('should skip disabled items when navigating prev', () => { const {toolbar, items} = getPatterns({ softDisabled: false, orientation: 'vertical', @@ -1178,7 +1163,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 0'); }); - it('should navigate to the last focusable item on End (vertical, rtl)', () => { + it('should navigate to the last focusable item on End', () => { const {toolbar, items} = getPatterns({ softDisabled: false, orientation: 'vertical', @@ -1190,7 +1175,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()).toBe(items[items.length - 2]); }); - it('should navigate to the first focusable item on Home (vertical, rtl)', () => { + it('should navigate to the first focusable item on Home', () => { const {toolbar, items} = getPatterns({ softDisabled: false, textDirection: 'rtl', @@ -1205,7 +1190,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap true', () => { - it('should wrap from last to first focusable item (vertical, rtl)', () => { + it('should wrap from last to first focusable item', () => { const {toolbar, items} = getPatterns({ wrap: true, softDisabled: false, @@ -1220,7 +1205,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 1'); }); - it('should wrap from first to last focusable item (vertical, rtl)', () => { + it('should wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ wrap: true, softDisabled: false, @@ -1237,7 +1222,7 @@ describe('Toolbar Pattern', () => { }); describe('with wrap false', () => { - it('should not wrap from last to first focusable item (vertical, rtl)', () => { + it('should not wrap from last to first focusable item', () => { const {toolbar} = getPatterns({ wrap: false, softDisabled: false, @@ -1250,7 +1235,7 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 8'); }); - it('should not wrap from first to last focusable item (vertical, rtl)', () => { + it('should not wrap from first to last focusable item', () => { const {toolbar, items} = getPatterns({ wrap: false, softDisabled: false, @@ -1270,7 +1255,7 @@ describe('Toolbar Pattern', () => { }); describe('with disabled toolbar', () => { - it('should not navigate on any key press (disabled toolbar)', () => { + it('should not navigate on any key press', () => { const {toolbar} = getPatterns({disabled: true}); const initialActiveItem = toolbar.activeItem(); @@ -1296,7 +1281,7 @@ describe('Toolbar Pattern', () => { }); describe('Selection', () => { - it('should toggle the active item on Enter (selection)', () => { + it('should toggle the active item on Enter', () => { const {toolbar} = getPatterns(); expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); toolbar.onKeydown(enter()); @@ -1305,7 +1290,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); }); - it('should toggle the active item on Space (selection)', () => { + it('should toggle the active item on Space', () => { const {toolbar} = getPatterns(); expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); toolbar.onKeydown(space()); @@ -1314,7 +1299,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); }); - it('should toggle the active item on click (selection)', () => { + it('should toggle the active item on click', () => { const {toolbar, items} = getPatterns(); expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); toolbar.onClick(clickItem(items[0])); @@ -1323,7 +1308,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); }); - it('should be able to select multiple items in the toolbar (selection)', () => { + it('should be able to select multiple items in the toolbar', () => { const {toolbar} = getPatterns(); expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); @@ -1340,7 +1325,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 1').selected()).toBeTrue(); }); - it('should not be able to select multiple items in a group (selection)', () => { + it('should not be able to select multiple items in a group', () => { const {toolbar} = getPatterns(); expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); expect(getItem(toolbar, 'item 3').selected()).toBeFalse(); @@ -1359,7 +1344,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 3').selected()).toBeTrue(); }); - it('should not select disabled items (selection)', () => { + it('should not select disabled items', () => { const {toolbar, items} = getPatterns(); items[1].inputs.disabled.set(true); @@ -1372,7 +1357,7 @@ describe('Toolbar Pattern', () => { expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); }); - it('should not select items in a disabled group (selection)', () => { + it('should not select items in a disabled group', () => { const {toolbar, items, group0} = getPatterns(); group0.disabled.set(true); @@ -1385,41 +1370,5 @@ describe('Toolbar Pattern', () => { expect(toolbar.activeItem()?.value()).toBe('item 3'); expect(getItem(toolbar, 'item 3').selected()).toBeFalse(); }); - }); // Close preceding section - - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[1]); // Set to item 1 - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 0'); // Should reset to item 0 - }); - - it('should NOT set default state if keyboard interacted', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[0]); - toolbar.onKeydown(right()); // Interaction (ArrowRight moves to item 1) - - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 (interacted) - }); - - it('should NOT set default state if pointer interacted', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[1]); - toolbar.onPointerdown(clickItem(items[1])); // Interaction - - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 - }); - - it('should NOT set default state if focus-in occurred', () => { - const {toolbar, items} = getPatterns(); - toolbar.inputs.activeItem.set(items[1]); - toolbar.onFocusIn(); // Interaction - - toolbar.setDefaultStateEffect(); - expect(toolbar.activeItem()?.value()).toBe('item 1'); // Should stay on item 1 - }); }); }); diff --git a/src/aria/private/toolbar/toolbar.ts b/src/aria/private/toolbar/toolbar.ts index 0e0124f92e09..accc30361b96 100644 --- a/src/aria/private/toolbar/toolbar.ts +++ b/src/aria/private/toolbar/toolbar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, signal, SignalLike} from '../behaviors/signal-like/signal-like'; +import {computed, SignalLike} from '../behaviors/signal-like/signal-like'; import {KeyboardEventManager} from '../behaviors/event-manager'; import {List, ListInputs} from '../behaviors/list/list'; import {ToolbarWidgetPattern} from './toolbar-widget'; @@ -25,9 +25,6 @@ export class ToolbarPattern { /** The list behavior for the toolbar. */ readonly listBehavior: List, V>; - /** Whether the toolbar has been interacted with. */ - readonly hasBeenInteracted = signal(false); - /** Whether the tablist is vertically or horizontally oriented. */ readonly orientation: SignalLike<'vertical' | 'horizontal'>; @@ -170,35 +167,16 @@ export class ToolbarPattern { }); } - /** Returns a set of violations */ - validate(): string[] { - const violations: string[] = []; - - const values = this.inputs.items().map(w => w.value()); - const duplicates = values.filter((val, idx) => values.indexOf(val) !== idx); - if (duplicates.length > 0) { - violations.push(`Duplicate value '${duplicates[0]}' detected inside ngToolbar.`); - } - - return violations; - } - /** Handles keydown events for the toolbar. */ onKeydown(event: KeyboardEvent) { if (this.disabled()) return; - this.hasBeenInteracted.set(true); this._keydown().handle(event); } onPointerdown(event: PointerEvent) { - this.hasBeenInteracted.set(true); event.preventDefault(); } - onFocusIn() { - this.hasBeenInteracted.set(true); - } - /** Handles click events for the toolbar. */ onClick(event: MouseEvent) { if (this.disabled() || (event as PointerEvent).pointerType === '') return; @@ -220,13 +198,4 @@ export class ToolbarPattern { this.inputs.activeItem.set(firstItem); } } - - /** Sets the default active state of the toolbar before receiving interaction for the first time. */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; - - if (this.inputs.items().length > 0) { - this.setDefaultState(); - } - } } diff --git a/src/aria/private/tree/BUILD.bazel b/src/aria/private/tree/BUILD.bazel index 287f3d90787e..91d1b20dd507 100644 --- a/src/aria/private/tree/BUILD.bazel +++ b/src/aria/private/tree/BUILD.bazel @@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"]) ts_project( name = "tree", srcs = [ + "combobox-tree.ts", "tree.ts", ], deps = [ @@ -13,6 +14,7 @@ ts_project( "//src/aria/private/behaviors/expansion", "//src/aria/private/behaviors/signal-like", "//src/aria/private/behaviors/tree", + "//src/aria/private/combobox", ], ) diff --git a/src/aria/private/tree/combobox-tree.ts b/src/aria/private/tree/combobox-tree.ts new file mode 100644 index 000000000000..fccec240ea6e --- /dev/null +++ b/src/aria/private/tree/combobox-tree.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {TreeInputs, TreePattern, TreeItemPattern} from './tree'; +import {computed, SignalLike} from '../behaviors/signal-like/signal-like'; +import {ComboboxPattern, ComboboxTreeControls} from '../combobox/combobox'; + +export type ComboboxTreeInputs = TreeInputs & { + /** The combobox controlling the tree. */ + combobox: SignalLike, V> | undefined>; +}; + +export class ComboboxTreePattern + extends TreePattern + implements ComboboxTreeControls, V> +{ + /** Toggles to expand or collapse a tree item. */ + toggleExpansion = (item?: TreeItemPattern) => this.treeBehavior.toggleExpansion(item); + + /** Whether the currently focused item is collapsible. */ + isItemCollapsible = () => this.inputs.activeItem()?.parent() instanceof TreeItemPattern; + + /** The ARIA role for the tree. */ + role = () => 'tree' as const; + + /* The id of the active (focused) item in the tree. */ + activeId = computed(() => this.treeBehavior.activeDescendant()); + + /** Returns the currently active (focused) item in the tree. */ + getActiveItem = () => this.inputs.activeItem(); + + /** The list of items in the tree. */ + override items = computed(() => this.inputs.items()); + + /** The tab index for the tree. Always -1 because the combobox handles focus. */ + override tabIndex: SignalLike<-1 | 0> = () => -1; + + constructor(override readonly inputs: ComboboxTreeInputs) { + if (inputs.combobox()) { + inputs.multi = () => false; + inputs.focusMode = () => 'activedescendant'; + inputs.element = inputs.combobox()!.inputs.inputEl; + } + + super(inputs); + } + + /** Noop. The combobox handles keydown events. */ + override onKeydown(_: KeyboardEvent): void {} + + /** Noop. The combobox handles pointerdown events. */ + override onPointerdown(_: PointerEvent): void {} + + /** Noop. The combobox controls the open state. */ + override setDefaultState(): void {} + + /** Navigates to the specified item in the tree. */ + focus = (item: TreeItemPattern) => this.treeBehavior.goto(item); + + /** Navigates to the next focusable item in the tree. */ + next = () => this.treeBehavior.next(); + + /** Navigates to the previous focusable item in the tree. */ + prev = () => this.treeBehavior.prev(); + + /** Navigates to the last focusable item in the tree. */ + last = () => this.treeBehavior.last(); + + /** Navigates to the first focusable item in the tree. */ + first = () => this.treeBehavior.first(); + + /** Unfocuses the currently focused item in the tree. */ + unfocus = () => this.treeBehavior.unfocus(); + + // TODO: handle non-selectable parent nodes. + /** Selects the specified item in the tree or the current active item if not provided. */ + select = (item?: TreeItemPattern) => this.treeBehavior.select(item); + + /** Toggles the selection state of the given item in the tree or the current active item if not provided. */ + toggle = (item?: TreeItemPattern) => this.treeBehavior.toggle(item); + + /** Clears the selection in the tree. */ + clearSelection = () => this.treeBehavior.deselectAll(); + + /** Retrieves the TreeItemPattern associated with a pointer event. */ + getItem = (e: PointerEvent) => this._getItem(e); + + /** Retrieves the currently selected items in the tree */ + getSelectedItems = () => this.inputs.items().filter(item => item.selected()); + + /** Sets the value of the combobox tree. */ + setValue = (value: V | undefined) => this.inputs.values.set(value ? [value] : []); + + /** Expands the currently focused item if it is expandable, or navigates to the first child. */ + expandItem = () => this._expandOrFirstChild(); + + /** Collapses the currently focused item if it is expandable, or navigates to the parent. */ + collapseItem = () => this._collapseOrParent(); + + /** Whether the specified item or the currently active item is expandable. */ + isItemExpandable(item: TreeItemPattern | undefined = this.inputs.activeItem()) { + return item ? item.expandable() : false; + } + + /** Expands all of the tree items. */ + expandAll = () => this.treeBehavior.expandAll(); + + /** Collapses all of the tree items. */ + collapseAll = () => this.treeBehavior.collapseAll(); + + /** Whether the currently active item is selectable. */ + isItemSelectable = (item: TreeItemPattern | undefined = this.inputs.activeItem()) => { + return item ? item.selectable() : false; + }; +} diff --git a/src/aria/private/tree/tree.spec.ts b/src/aria/private/tree/tree.spec.ts index a9a241634fa6..708b406ec7ce 100644 --- a/src/aria/private/tree/tree.spec.ts +++ b/src/aria/private/tree/tree.spec.ts @@ -153,7 +153,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -207,7 +207,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(true), currentType: signal('page'), @@ -218,7 +218,7 @@ describe('Tree Pattern', () => { it('should have undefined selected state', () => { const {items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.selected()).toBeUndefined(); }); @@ -227,11 +227,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.current()).toBe('page'); expect(item1.current()).toBeUndefined(); - treeInputs.value.set(['Item 1']); + treeInputs.values.set(['Item 1']); treeInputs.currentType.set('step'); expect(item0.current()).toBeUndefined(); expect(item1.current()).toBe('step'); @@ -240,7 +240,7 @@ describe('Tree Pattern', () => { it('should have undefined current state when non-selectable', () => { const {items, itemPatternInputsMap} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.current()).toBe('page'); itemPatternInputsMap.get(item0.id())!.selectable.set(false); expect(item0.current()).toBeUndefined(); @@ -263,7 +263,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -449,7 +449,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -462,11 +462,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.selected()).toBe(true); expect(item1.selected()).toBe(false); - treeInputs.value.set(['Item 1']); + treeInputs.values.set(['Item 1']); expect(item0.selected()).toBe(false); expect(item1.selected()).toBe(true); }); @@ -474,31 +474,31 @@ describe('Tree Pattern', () => { it('should have undefined selected state when non-selectable', () => { const {items, itemPatternInputsMap} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); itemPatternInputsMap.get(item0.id())!.selectable.set(false); expect(item0.selected()).toBeUndefined(); }); - it('should select an item on navigation in follows focus & single select mode', () => { + it('should select an item on navigation', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); tree.onKeydown(down()); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); tree.onKeydown(up()); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); - it('should not change selection when the tree is disabled in follows focus & single select mode', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -517,7 +517,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -525,60 +525,60 @@ describe('Tree Pattern', () => { }; }); - it('should select an item on Space in explicit focus & single select mode', () => { + it('should select an item on Space', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should not deselect an item on Space', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); - it('should select an item on Enter in explicit focus & single select mode', () => { + it('should select an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should not deselect an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should only allow one selected item', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(down()); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); - it('should not change selection when the tree is disabled in explicit focus & single select mode', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -597,7 +597,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -605,18 +605,18 @@ describe('Tree Pattern', () => { }; }); - it('should select an item on Space in explicit focus & multi select mode', () => { + it('should select an item on Space', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); - it('should select an item on Enter in explicit focus & multi select mode', () => { + it('should select an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should allow multiple selected items', () => { @@ -625,52 +625,52 @@ describe('Tree Pattern', () => { tree.onKeydown(enter()); tree.onKeydown(down()); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); }); - it('should select a range of visible items on Shift + ArrowDown/ArrowUp in explicit focus & multi select mode', () => { + it('should select a range of visible items on Shift + ArrowDown/ArrowUp', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); item0.expanded.set(true); tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); tree.onKeydown(up({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); }); - it('should not allow wrapping while Shift is held down in explicit focus & multi select mode', () => { + it('should not allow wrapping while Shift is held down', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); tree.onKeydown(shift()); tree.onKeydown(up({shift: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); - it('should select a range of visible items on Shift + Space (or Enter) in explicit focus & multi select mode', () => { + it('should select a range of visible items on Shift + Space (or Enter)', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); item0.expanded.set(true); tree.onKeydown(down()); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); tree.onKeydown(down()); tree.onKeydown(down()); tree.onKeydown(shift()); tree.onKeydown(space({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1']); }); - it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home in explicit focus & multi select mode', () => { + it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); @@ -679,10 +679,10 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(home({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); + expect(tree.inputs.values()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); }); - it('should select the focused item and all visible items down to the last on Ctrl + Shift + End in explicit focus & multi select mode', () => { + it('should select the focused item and all visible items down to the last on Ctrl + Shift + End', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item0_0 = getItemByValue(items(), 'Item 0-0'); @@ -691,18 +691,18 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(end({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); }); - it('should not change selection when the tree is disabled in explicit focus & multi select mode', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not select disabled items on Shift + ArrowUp / ArrowDown', () => { @@ -719,7 +719,7 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['A', 'C']); + expect(tree.inputs.values()).toEqual(['A', 'C']); }); it('should not select non-selectable items on Shift + ArrowUp / ArrowDown', () => { @@ -735,7 +735,7 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['A', 'C']); + expect(tree.inputs.values()).toEqual(['A', 'C']); }); it('should select all visible items on Ctrl + A', () => { @@ -744,7 +744,13 @@ describe('Tree Pattern', () => { item0.expanded.set(true); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); }); it('should deselect all visible items on Ctrl + A if all are selected', () => { @@ -754,7 +760,7 @@ describe('Tree Pattern', () => { tree.onKeydown(a({control: true})); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -773,7 +779,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -781,49 +787,49 @@ describe('Tree Pattern', () => { }; }); - it('should select an item on navigation in follows focus & multi select mode', () => { + it('should select an item on navigation', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate without selecting if the Ctrl key is pressed', () => { - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); tree.onKeydown(down({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); expect(tree.activeItem()).toBe(item1); }); it('should toggle an item selection state on Ctrl + Space', () => { - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down({control: true})); tree.onKeydown(space({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); tree.onKeydown(space({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); - it('should select a range of visible items on Shift + ArrowDown/ArrowUp in follows focus & multi select mode', () => { - treeInputs.value.set(['Item 0']); + it('should select a range of visible items on Shift + ArrowDown/ArrowUp', () => { + treeInputs.values.set(['Item 0']); const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); item0.expanded.set(true); tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); }); - it('should not allow wrapping while Shift is held down in follows focus & multi select mode', () => { + it('should not allow wrapping while Shift is held down', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); tree.treeBehavior.goto(item0); @@ -831,10 +837,10 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(up({shift: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); - it('should select a range of visible items on Shift + Space (or Enter) in follows focus & multi select mode', () => { + it('should select a range of visible items on Shift + Space (or Enter)', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); item0.expanded.set(true); @@ -844,10 +850,10 @@ describe('Tree Pattern', () => { tree.onKeydown(down({control: true})); tree.onKeydown(shift()); tree.onKeydown(space({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); }); - it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home in follows focus & multi select mode', () => { + it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); @@ -856,10 +862,10 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(home({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); + expect(tree.inputs.values()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); }); - it('should select the focused item and all visible items down to the last on Ctrl + Shift + End in follows focus & multi select mode', () => { + it('should select the focused item and all visible items down to the last on Ctrl + Shift + End', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item0_0 = getItemByValue(items(), 'Item 0-0'); @@ -868,15 +874,15 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(end({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); }); - it('should not change selection when the tree is disabled in follows focus & multi select mode', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not select disabled items on navigation', () => { @@ -887,11 +893,11 @@ describe('Tree Pattern', () => { ]; treeInputs.softDisabled.set(false); const {tree, items} = createTree(localTreeData, treeInputs); - treeInputs.value.set(['A']); + treeInputs.values.set(['A']); tree.treeBehavior.goto(getItemByValue(items(), 'A')); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['C']); + expect(tree.inputs.values()).toEqual(['C']); }); it('should not select non-selectable items on navigation', () => { @@ -901,12 +907,12 @@ describe('Tree Pattern', () => { {value: 'C', disabled: false, selectable: true, expanded: false}, ]; const {tree, items} = createTree(localTreeData, treeInputs); - treeInputs.value.set(['A']); + treeInputs.values.set(['A']); tree.treeBehavior.goto(getItemByValue(items(), 'A')); tree.onKeydown(down()); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['C']); + expect(tree.inputs.values()).toEqual(['C']); }); it('should deselect all except the focused item on Ctrl + A if all are selected', () => { @@ -917,9 +923,15 @@ describe('Tree Pattern', () => { tree.treeBehavior.goto(item0_0); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); }); }); }); @@ -940,7 +952,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -948,22 +960,22 @@ describe('Tree Pattern', () => { }; }); - it('should navigate and select a single item on click in follows focus & single select mode', () => { + it('should navigate and select a single item on click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item1.element()!)); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); - it('should not change selection when the tree is disabled in follows focus & single select mode (mouse)', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item1.element()!)); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -982,7 +994,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -990,35 +1002,35 @@ describe('Tree Pattern', () => { }; }); - it('should navigate and toggle selection on click in explicit focus & single select mode', () => { + it('should navigate and toggle selection on click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item1.element()!)); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should not deselect item on click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item1.element()!)); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); - tree.onClick(createClickEvent(item1.element()!)); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); - it('should not change selection when the tree is disabled in explicit focus & single select mode (mouse)', () => { + it('should not change selection when the tree is disabled', () => { treeInputs.disabled.set(true); const {tree, items} = createTree(treeExample, treeInputs); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item1.element()!)); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -1037,7 +1049,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1045,30 +1057,30 @@ describe('Tree Pattern', () => { }; }); - it('should navigate and toggle selection on click in explicit focus & multi select mode', () => { + it('should navigate and toggle selection on click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item0.element()!)); - expect(tree.inputs.value()).toEqual(['Item 0']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0']); - tree.onClick(createClickEvent(item1.element()!)); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); - tree.onClick(createClickEvent(item0.element()!)); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 1']); }); - it('should navigate and select range from anchor on shift + click in explicit focus & multi select mode', () => { + it('should navigate and select range from anchor on shift + click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); item0.expanded.set(true); tree.onKeydown(shift()); - tree.onClick(createClickEvent(item1.element()!, {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); }); }); @@ -1087,7 +1099,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1095,15 +1107,15 @@ describe('Tree Pattern', () => { }; }); - it('should navigate and select a single item on click in follows focus & multi select mode', () => { + it('should navigate and select a single item on click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item0.element()!)); - expect(tree.inputs.value()).toEqual(['Item 0']); - tree.onClick(createClickEvent(item1.element()!)); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0']); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate and toggle selection on ctrl + click', () => { @@ -1111,22 +1123,28 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(items(), 'Item 0'); const item1 = getItemByValue(items(), 'Item 1'); - tree.onClick(createClickEvent(item0.element()!)); // Select and expand Item 0 - tree.onClick(createClickEvent(item1.element()!, {control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); - tree.onClick(createClickEvent(item0.element()!, {control: true})); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); // Select and expand Item 0 + tree.onPointerdown(createClickEvent(item1.element()!, {control: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!, {control: true})); + expect(tree.inputs.values()).toEqual(['Item 1']); }); - it('should navigate and select range from anchor on shift + click in follows focus & multi select mode', () => { + it('should navigate and select range from anchor on shift + click', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); const item2 = getItemByValue(items(), 'Item 2'); - tree.onClick(createClickEvent(item0.element()!)); // Select and expand Item 0 + tree.onPointerdown(createClickEvent(item0.element()!)); // Select and expand Item 0 tree.onKeydown(shift()); - tree.onClick(createClickEvent(item2.element()!, {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + tree.onPointerdown(createClickEvent(item2.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); }); it('should select a new range on subsequent shift + clicks, deselecting previous range', () => { @@ -1137,11 +1155,11 @@ describe('Tree Pattern', () => { item0.expanded.set(true); tree.onKeydown(shift()); - tree.onClick(createClickEvent(item1.element()!, {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); - tree.onClick(createClickEvent(item0_0.element()!, {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + tree.onPointerdown(createClickEvent(item0_0.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); }); it('should not select disabled items on click', () => { @@ -1151,8 +1169,8 @@ describe('Tree Pattern', () => { const {tree, items} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(items(), 'A'); - tree.onClick(createClickEvent(itemA.element()!)); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(itemA.element()!)); + expect(tree.inputs.values()).toEqual([]); expect(tree.activeItem()).toBe(itemA); }); @@ -1162,8 +1180,8 @@ describe('Tree Pattern', () => { ]; const {tree, items} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(items(), 'A'); - tree.onClick(createClickEvent(itemA.element()!)); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(itemA.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); }); @@ -1183,7 +1201,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1332,9 +1350,9 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(items(), 'Item 0'); expect(item0.expanded()).toBe(false); - tree.onClick(createClickEvent(item0.element()!)); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(true); - tree.onClick(createClickEvent(item0.element()!)); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1343,7 +1361,7 @@ describe('Tree Pattern', () => { const item1 = getItemByValue(items(), 'Item 1'); expect(item1.expanded()).toBe(false); - tree.onClick(createClickEvent(item1.element()!)); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(item1.expanded()).toBe(false); }); @@ -1352,7 +1370,7 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(items(), 'Item 0'); itemPatternInputsMap.get(item0.id())!.disabled.set(true); - tree.onClick(createClickEvent(item0.element()!)); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1361,7 +1379,7 @@ describe('Tree Pattern', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); - tree.onClick(createClickEvent(item0.element()!)); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1381,7 +1399,7 @@ describe('Tree Pattern', () => { tree.onKeydown(right()); expect(tree.activeItem()).toBe(item0_0); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); }); it('should navigate and select the parent on collapseKey if collapsed (vertical)', () => { @@ -1394,7 +1412,7 @@ describe('Tree Pattern', () => { tree.onKeydown(left()); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); }); @@ -1411,11 +1429,11 @@ describe('Tree Pattern', () => { const item0_0 = getItemByValue(items(), 'Item 0-0'); tree.treeBehavior.goto(item0); item0.expanded.set(true); - tree.inputs.value.set(['Item 1']); // pre-select something else + tree.inputs.values.set(['Item 1']); // pre-select something else tree.onKeydown(right({control: true})); expect(tree.activeItem()).toBe(item0_0); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate without select the parent on Ctrl + collapseKey if collapsed (vertical)', () => { @@ -1425,11 +1443,11 @@ describe('Tree Pattern', () => { const item0_0 = getItemByValue(items(), 'Item 0-0'); item0.expanded.set(true); tree.treeBehavior.goto(item0_0); - tree.inputs.value.set(['Item 1']); // pre-select something else + tree.inputs.values.set(['Item 1']); // pre-select something else tree.onKeydown(left({control: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); }); }); @@ -1449,7 +1467,7 @@ describe('Tree Pattern', () => { softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1486,7 +1504,7 @@ describe('Tree Pattern', () => { {value: 'B', disabled: false, selectable: true, expanded: false}, {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); + treeInputs.values.set(['B']); const {tree, items} = createTree(localTreeData, treeInputs); tree.setDefaultState(); @@ -1499,7 +1517,7 @@ describe('Tree Pattern', () => { {value: 'B', disabled: false, selectable: true, expanded: false}, {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['C', 'A']); + treeInputs.values.set(['C', 'A']); const {tree, items} = createTree(localTreeData, treeInputs); tree.setDefaultState(); @@ -1512,7 +1530,7 @@ describe('Tree Pattern', () => { {value: 'B', disabled: true, selectable: true, expanded: false}, {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); + treeInputs.values.set(['B']); treeInputs.softDisabled.set(false); const {tree, items} = createTree(localTreeData, treeInputs); @@ -1526,7 +1544,7 @@ describe('Tree Pattern', () => { {value: 'B', disabled: true, selectable: true, expanded: false}, {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); + treeInputs.values.set(['B']); treeInputs.softDisabled.set(true); const {tree, items} = createTree(localTreeData, treeInputs); @@ -1537,51 +1555,12 @@ describe('Tree Pattern', () => { it('should set activeIndex to first visible focusable item if selected item is not visible', () => { const {tree, items} = createTree(treeExample, treeInputs); const item0 = getItemByValue(items(), 'Item 0'); - treeInputs.value.set(['Item 0-0']); + treeInputs.values.set(['Item 0-0']); expect(item0.expanded()).toBe(false); expect(getItemByValue(items(), 'Item 0-0').visible()).toBe(false); tree.setDefaultState(); expect(treeInputs.activeItem()).toBe(item0); }); - describe('#setDefaultStateEffect', () => { - it('should set default state if not interacted', () => { - const {tree, items} = createTree(treeExample, treeInputs); - const item2 = getItemByValue(items(), 'Item 2'); - treeInputs.value.set(['Item 2']); - tree.setDefaultStateEffect(); - expect(treeInputs.activeItem()).toBe(item2); // Should reset to selected Item 2 - }); - - it('should NOT set default state if keyboard interacted', () => { - const {tree, items} = createTree(treeExample, treeInputs); - tree.onKeydown(down()); // Interaction (ArrowDown moves to item1) - - const item1 = getItemByValue(items(), 'Item 1'); - treeInputs.value.set(['Item 2']); - tree.setDefaultStateEffect(); - expect(treeInputs.activeItem()).toBe(item1); // Should stay on item1 - }); - - it('should NOT set default state if pointer interacted', () => { - const {tree, items} = createTree(treeExample, treeInputs); - const item0 = getItemByValue(items(), 'Item 0'); - tree.onClick(createClickEvent(item0.element()!)); // Interaction - - treeInputs.value.set(['Item 2']); - tree.setDefaultStateEffect(); - expect(treeInputs.activeItem()).toBe(item0); // Should stay on item0 - }); - - it('should NOT set default state if focus-in occurred', () => { - const {tree, items} = createTree(treeExample, treeInputs); - tree.onFocusIn(); // Interaction - - const item0 = getItemByValue(items(), 'Item 0'); - treeInputs.value.set(['Item 2']); - tree.setDefaultStateEffect(); - expect(treeInputs.activeItem()).toBe(item0); // Should stay on item0 - }); - }); }); }); diff --git a/src/aria/private/tree/tree.ts b/src/aria/private/tree/tree.ts index d97782f7b50b..afc004047d78 100644 --- a/src/aria/private/tree/tree.ts +++ b/src/aria/private/tree/tree.ts @@ -6,14 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - SignalLike, - computed, - WritableSignalLike, - signal, -} from '../behaviors/signal-like/signal-like'; +import {SignalLike, computed, WritableSignalLike} from '../behaviors/signal-like/signal-like'; import {Tree, TreeItem, TreeInputs as TreeBehaviorInputs} from '../behaviors/tree/tree'; -import {KeyboardEventManager, Modifier, ClickEventManager} from '../behaviors/event-manager'; +import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; /** Represents the required inputs for a tree item. */ export interface TreeItemInputs extends Omit< @@ -101,7 +96,7 @@ export class TreeItemPattern implements TreeItem> { if (!this.selectable()) { return undefined; } - return this.tree().value().includes(this.value()); + return this.tree().values().includes(this.value()); }); /** The current type of this item. */ @@ -112,7 +107,7 @@ export class TreeItemPattern implements TreeItem> { if (!this.selectable()) { return undefined; } - return this.tree().value().includes(this.value()) ? this.tree().currentType() : undefined; + return this.tree().values().includes(this.value()) ? this.tree().currentType() : undefined; }); constructor(readonly inputs: TreeItemInputs) { @@ -151,9 +146,6 @@ export class TreePattern implements TreeInputs { /** The tree behavior for the tree. */ readonly treeBehavior: Tree, V>; - /** Whether the tree has been interacted with. */ - readonly hasBeenInteracted = signal(false); - /** The root level is 0. */ readonly level = () => 0; @@ -178,7 +170,7 @@ export class TreePattern implements TreeInputs { readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow'); /** Whether the tree direction is RTL. */ - readonly isRtl = computed(() => this.textDirection() === 'rtl'); + readonly isRtl = computed(() => this.inputs.textDirection() === 'rtl'); /** The key for navigating to the previous item. */ readonly prevKey = computed(() => { @@ -287,26 +279,26 @@ export class TreePattern implements TreeInputs { return manager; }); - /** The click event manager for the tree. */ - readonly clickManager = computed(() => { - const manager = new ClickEventManager(); + /** The pointerdown event manager for the tree. */ + pointerdown = computed(() => { + const manager = new PointerEventManager(); if (this.multi()) { - manager.on(Modifier.Shift, (e: PointerEvent) => this.goto(e, {selectRange: true})); + manager.on(Modifier.Shift, e => this.goto(e, {selectRange: true})); } if (!this.multi()) { - return manager.on((e: PointerEvent) => this.goto(e, {selectOne: true})); + return manager.on(e => this.goto(e, {selectOne: true})); } if (this.multi() && this.followFocus()) { return manager - .on((e: PointerEvent) => this.goto(e, {selectOne: true})) - .on(Modifier.Ctrl, (e: PointerEvent) => this.goto(e, {toggle: true})); + .on(e => this.goto(e, {selectOne: true})) + .on(Modifier.Ctrl, e => this.goto(e, {toggle: true})); } if (this.multi() && !this.followFocus()) { - return manager.on((e: PointerEvent) => this.goto(e, {toggle: true})); + return manager.on(e => this.goto(e, {toggle: true})); } return manager; @@ -348,7 +340,7 @@ export class TreePattern implements TreeInputs { readonly orientation: SignalLike<'vertical' | 'horizontal'> = () => this.inputs.orientation(); /** The text direction of the tree. */ - readonly textDirection: SignalLike<'ltr' | 'rtl'> = () => this.inputs.textDirection(); + readonly textDirection: SignalLike<'ltr' | 'rtl'> = () => this.textDirection(); /** Whether multiple items can be selected at the same time. */ readonly multi: SignalLike = computed(() => (this.nav() ? false : this.inputs.multi())); @@ -360,11 +352,11 @@ export class TreePattern implements TreeInputs { readonly typeaheadDelay: SignalLike = () => this.inputs.typeaheadDelay(); /** The current selected items of the tree. */ - readonly value: WritableSignalLike; + readonly values: WritableSignalLike; constructor(readonly inputs: TreeInputs) { this.activeItem = inputs.activeItem; - this.value = inputs.value; + this.values = inputs.values; this.treeBehavior = new Tree, V>({ ...inputs, @@ -377,18 +369,12 @@ export class TreePattern implements TreeInputs { validate(): string[] { const violations: string[] = []; - if (!this.inputs.multi() && this.inputs.value().length > 1) { + if (!this.inputs.multi() && this.inputs.values().length > 1) { violations.push( - `A single-select tree should not have multiple selected options. Selected options: ${this.inputs.value().join(', ')}`, + `A single-select tree should not have multiple selected options. Selected options: ${this.inputs.values().join(', ')}`, ); } - const values = this.inputs.items().map(t => t.value()); - const duplicates = values.filter((val, idx) => values.indexOf(val) !== idx); - if (duplicates.length > 0) { - violations.push(`Duplicate tree item value '${duplicates[0]}' detected inside ngTree.`); - } - return violations; } @@ -420,34 +406,20 @@ export class TreePattern implements TreeInputs { } } - /** Sets the default active state of the tree before receiving interaction for the first time. */ - setDefaultStateEffect(): void { - if (this.hasBeenInteracted()) return; - - this.setDefaultState(); - } - /** Handles keydown events on the tree. */ onKeydown(event: KeyboardEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); this.keydown().handle(event); } } - /** Handles click events on the tree. */ - onClick(event: PointerEvent) { + /** Handles pointerdown events on the tree. */ + onPointerdown(event: PointerEvent) { if (!this.disabled()) { - this.hasBeenInteracted.set(true); - this.clickManager().handle(event); + this.pointerdown().handle(event); } } - /** Handles focusin events on the tree. */ - onFocusIn() { - this.hasBeenInteracted.set(true); - } - /** Navigates to the given tree item in the tree. */ goto(e: PointerEvent, opts?: SelectOptions) { const item = this._getItem(e); diff --git a/src/aria/private/utils/collection.spec.ts b/src/aria/private/utils/collection.spec.ts deleted file mode 100644 index 0745e1e3e0d2..000000000000 --- a/src/aria/private/utils/collection.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {SortedCollection} from './collection'; -import {HasElement} from './element'; -import { - signal, - Component, - ElementRef, - inject, - OnDestroy, - afterNextRender, - Input, -} from '@angular/core'; -import {waitForMicrotasks} from '../testing/test-helpers'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {By} from '@angular/platform-browser'; - -describe('SortedCollection with real directives', () => { - let fixture: ComponentFixture; - let testComponent: TestComponent; - let collectionDirective: TestCollectionComponent; - - beforeEach(async () => { - TestBed.configureTestingModule({ - providers: [], - }); - - fixture = TestBed.createComponent(TestComponent); - testComponent = fixture.componentInstance; - - fixture.detectChanges(); - await waitForMicrotasks(); - - const collectionEl = fixture.debugElement.query(By.directive(TestCollectionComponent)); - collectionDirective = collectionEl.injector.get(TestCollectionComponent); - }); - - afterEach(() => { - collectionDirective._collection.stopObserving(); - }); - - it('should register items initially', () => { - const orderedItems = collectionDirective._collection.orderedItems(); - expect(orderedItems.length).toBe(3); - expect(orderedItems[0].value).toBe('A'); - expect(orderedItems[2].value).toBe('C'); - }); - - it('should unregister items when removed from DOM', async () => { - testComponent.items.set(['A', 'C']); - fixture.detectChanges(); - await waitForMicrotasks(); - - const orderedItems = collectionDirective._collection.orderedItems(); - expect(orderedItems.length).toBe(2); - expect(orderedItems[0].value).toBe('A'); - expect(orderedItems[1].value).toBe('C'); - }); - - it('should maintain initial collection order before shuffling', () => { - const orderedItems = collectionDirective._collection.orderedItems(); - - expect(orderedItems.length).toBe(3); - expect(orderedItems[0].value).toBe('A'); - expect(orderedItems[2].value).toBe('C'); - }); - - it('should update collection order when items are shuffled', async () => { - testComponent.items.set(['C', 'B', 'A']); - fixture.detectChanges(); - await waitForMicrotasks(); - - const orderedItems = collectionDirective._collection.orderedItems(); - - expect(orderedItems.length).toBe(3); - expect(orderedItems[0].value).toBe('C'); - expect(orderedItems[2].value).toBe('A'); - }); -}); - -@Component({ - selector: 'testCollection', - template: '', -}) -class TestCollectionComponent implements HasElement, OnDestroy { - readonly _collection = new SortedCollection(); - readonly element = inject(ElementRef).nativeElement as HTMLElement; - - constructor() { - afterNextRender(() => { - this._collection.startObserving(this.element); - }); - } - - ngOnDestroy() { - this._collection.stopObserving(); - } -} - -@Component({ - selector: 'testItem', - template: ` -
{{value}}
- `, -}) -class TestItemComponent implements HasElement, OnDestroy { - readonly element = inject(ElementRef).nativeElement as HTMLElement; - private readonly _parent = inject(TestCollectionComponent); - - @Input() value!: string; - - constructor() { - afterNextRender(() => { - this._parent._collection.register(this); - }); - } - - ngOnDestroy() { - this._parent._collection.unregister(this); - } -} - -@Component({ - template: ` - - @for (item of items(); track item) { - - } - - `, - imports: [TestCollectionComponent, TestItemComponent], -}) -class TestComponent { - readonly items = signal(['A', 'B', 'C']); -} diff --git a/src/aria/private/utils/collection.ts b/src/aria/private/utils/collection.ts deleted file mode 100644 index 0cabc6343401..000000000000 --- a/src/aria/private/utils/collection.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {signal, computed, Signal} from '@angular/core'; -import {sortDirectives, HasElement} from './element'; - -/** - * A collection that lazily sorts its items based on their DOM position. - * It uses manual registration and updates its order when items are added/removed - * or when structural DOM changes are detected via MutationObserver. - * - * TODO(ok7sai): replace Mutation Observer with internal API. - */ -export class SortedCollection { - private readonly _items = signal>(new Set()); - private readonly _version = signal(0); - private _observer?: MutationObserver; - - readonly orderedItems: Signal = computed(() => { - this._version(); // Track DOM changes - const itemsArray = Array.from(this._items()); - return itemsArray.sort(sortDirectives); - }); - - register(item: T) { - this._items.update(set => { - const newSet = new Set(set); - newSet.add(item); - return newSet; - }); - } - - unregister(item: T) { - this._items.update(set => { - const newSet = new Set(set); - newSet.delete(item); - return newSet; - }); - } - - startObserving(element: HTMLElement) { - if (this._observer) { - this._observer.disconnect(); - } - - this._observer = new MutationObserver(mutations => { - const hasStructuralChange = mutations.some(m => m.addedNodes.length || m.removedNodes.length); - if (hasStructuralChange) { - this._version.update(v => v + 1); - } - }); - - this._observer.observe(element, {childList: true, subtree: true}); - } - - stopObserving() { - this._observer?.disconnect(); - this._observer = undefined; - } -} diff --git a/src/aria/private/utils/element-resolver.spec.ts b/src/aria/private/utils/element-resolver.spec.ts deleted file mode 100644 index 65da551dae3a..000000000000 --- a/src/aria/private/utils/element-resolver.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import {ElementRef} from '@angular/core'; -import {resolveElement} from './element-resolver'; - -describe('ElementResolver', () => { - let context: HTMLElement; - - beforeEach(() => { - context = document.createElement('div'); - context.id = 'context-host'; - }); - - describe('resolveElement', () => { - it('should resolve a direct DOM element', () => { - const target = document.createElement('span'); - expect(resolveElement(target, context)).toBe(target); - }); - - it('should resolve an ElementRef transparently', () => { - const target = document.createElement('span'); - const elementRef = new ElementRef(target); - expect(resolveElement(elementRef, context)).toBe(target); - }); - - it('should resolve null as undefined', () => { - expect(resolveElement(null, context)).toBeUndefined(); - }); - - it('should resolve undefined as undefined', () => { - expect(resolveElement(undefined, context)).toBeUndefined(); - }); - - it('should evaluate a resolution function', () => { - const target = document.createElement('span'); - const resolver = (ctx: HTMLElement) => { - expect(ctx).toBe(context); - return target; - }; - expect(resolveElement(resolver, context)).toBe(target); - }); - - it('should evaluate a resolution function returning null', () => { - const resolver = () => null; - expect(resolveElement(resolver, context)).toBeUndefined(); - }); - - it('should evaluate a resolution function returning undefined', () => { - const resolver = () => undefined; - expect(resolveElement(resolver, context)).toBeUndefined(); - }); - }); -}); diff --git a/src/aria/private/utils/element-resolver.ts b/src/aria/private/utils/element-resolver.ts deleted file mode 100644 index f6acff92dbed..000000000000 --- a/src/aria/private/utils/element-resolver.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ElementRef} from '@angular/core'; - -/** A type that allows lazy resolution of a DOM Element. */ -export type ElementResolver = - | ElementRef - | T - | undefined - | null - | ((context: HTMLElement) => T | null | undefined); - -/** Evaluates an ElementResolver to return the underlying DOM element, or undefined. */ -export function resolveElement( - resolver: ElementResolver, - context: HTMLElement, -): T | undefined { - if (typeof resolver === 'function') { - return (resolver as Function)(context) ?? undefined; - } - return (resolver instanceof ElementRef ? resolver.nativeElement : resolver) ?? undefined; -} diff --git a/src/aria/private/utils/transforms.ts b/src/aria/private/utils/transforms.ts deleted file mode 100644 index 241273994bd0..000000000000 --- a/src/aria/private/utils/transforms.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {numberAttribute} from '@angular/core'; - -/** - * Transform function for tabIndex inputs. - * Returns undefined if the value is undefined, otherwise converts it to a number. - */ -export function tabIndexTransform(v: string | number | undefined): number | undefined { - return v === undefined ? undefined : numberAttribute(v); -} diff --git a/src/aria/private/utils/violations.ts b/src/aria/private/utils/violations.ts deleted file mode 100644 index ed1565a99f5e..000000000000 --- a/src/aria/private/utils/violations.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -/** Logs each of the violations to the console as errors, optionally with the host element context. */ -export function reportViolations(violations: string[], element: Element): void { - if (violations.length) { - console.warn('Violations found on element: %o:', element); - violations.forEach(violation => { - console.warn(violation); - }); - } -} diff --git a/src/aria/tabs/BUILD.bazel b/src/aria/tabs/BUILD.bazel index 074d863bc417..13aa715fbabc 100644 --- a/src/aria/tabs/BUILD.bazel +++ b/src/aria/tabs/BUILD.bazel @@ -11,7 +11,6 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//src/aria/private", - "//src/aria/private/utils", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -27,7 +26,6 @@ ng_project( ":tabs", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//src/aria/private/testing", "//src/cdk/testing/private", ], ) diff --git a/src/aria/tabs/tab-content.ts b/src/aria/tabs/tab-content.ts index 9188094c9606..872847e61ba9 100644 --- a/src/aria/tabs/tab-content.ts +++ b/src/aria/tabs/tab-content.ts @@ -24,6 +24,8 @@ import {DeferredContent} from '../private'; *
* ``` * + * @developerPreview 21.0 + * * @see [Tabs](guide/aria/tabs) */ @Directive({ diff --git a/src/aria/tabs/tab-list.ts b/src/aria/tabs/tab-list.ts index 6cccfb77ab72..7e9d4b12f413 100644 --- a/src/aria/tabs/tab-list.ts +++ b/src/aria/tabs/tab-list.ts @@ -19,12 +19,9 @@ import { afterRenderEffect, OnInit, OnDestroy, - afterNextRender, - linkedSignal, - WritableSignal, } from '@angular/core'; -import {SortedCollection, TabListPattern, TabPattern, reportViolations} from '../private'; -import {TABS, TAB_LIST} from './tab-tokens'; +import {TabListPattern, TabPattern} from '../private'; +import {sortDirectives, TABS} from './utils'; import type {Tab} from './tab'; /** @@ -41,6 +38,8 @@ import type {Tab} from './tab'; * * ``` * + * @developerPreview 21.0 + * * @see [Tabs](guide/aria/tabs) */ @Directive({ @@ -53,10 +52,9 @@ import type {Tab} from './tab'; '[attr.aria-orientation]': '_pattern.orientation()', '[attr.aria-activedescendant]': '_pattern.activeDescendant()', '(keydown)': '_pattern.onKeydown($event)', - '(click)': '_pattern.onClick($event)', - '(focusin)': '_pattern.onFocusIn()', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, - providers: [{provide: TAB_LIST, useExisting: TabList}], }) export class TabList implements OnInit, OnDestroy { /** A reference to the host element. */ @@ -65,23 +63,23 @@ export class TabList implements OnInit, OnDestroy { /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The parent Tabs container. */ - readonly _tabsParent = inject(TABS); + /** The parent Tabs. */ + private readonly _tabs = inject(TABS); + + /** The Tabs nested inside of the TabList. */ + private readonly _unorderedTabs = signal(new Set()); - /** The collection of Tabs. */ - readonly _collection = new SortedCollection(); + /** Text direction. */ + readonly textDirection = inject(Directionality).valueSignal; /** The Tab UIPatterns of the child Tabs. */ readonly _tabPatterns = computed(() => - this._collection.orderedItems().map(tab => tab._pattern), + [...this._unorderedTabs()].sort(sortDirectives).map(tab => tab._pattern), ); /** Whether the tablist is vertically or horizontally oriented. */ readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); - /** Text direction. */ - readonly textDirection = inject(Directionality).valueSignal; - /** Whether focus should wrap when navigating. */ readonly wrap = input(true, {transform: booleanAttribute}); @@ -105,82 +103,72 @@ export class TabList implements OnInit, OnDestroy { */ readonly selectionMode = input<'follow' | 'explicit'>('follow'); - /** The current selected tab as a model input. */ + /** The current selected tab. */ readonly selectedTab = model(); - /** The current selected Tab pattern, passed to the List pattern. */ - private readonly _selectedTabPattern: WritableSignal = linkedSignal( - () => { - const tab = this.findTab(this.selectedTab()); - - return tab?._pattern; - }, - ); - /** Whether the tablist is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); /** The TabList UIPattern. */ readonly _pattern: TabListPattern = new TabListPattern({ ...this, - element: () => this._elementRef.nativeElement, - activeItem: signal(undefined), items: this._tabPatterns, - selectedTab: this._selectedTabPattern, + activeItem: signal(undefined), + element: () => this._elementRef.nativeElement, }); + /** Whether the tree has received focus yet. */ + private _hasFocused = signal(false); + constructor() { - afterNextRender(() => { - this._collection.startObserving(this.element); + afterRenderEffect(() => { + if (!this._hasFocused()) { + this._pattern.setDefaultState(); + } }); afterRenderEffect(() => { - this._pattern.setDefaultStateEffect(); + const tab = this._pattern.selectedTab(); + if (tab) { + this.selectedTab.set(tab.value()); + } }); - // This needs to be in an afterRenderEffect to ensure the tabs have all been initialized. - // Otherwise, the lookup here can fail and it does not get re-run afterwards. - afterRenderEffect({ - write: () => { - const pattern = this._selectedTabPattern(); - const tab = this._collection.orderedItems().find(tab => tab._pattern == pattern); - this.selectedTab.set(tab?.value()); - }, + afterRenderEffect(() => { + const value = this.selectedTab(); + if (value) { + this._tabPatterns().forEach(tab => tab.expanded.set(false)); + const tab = this._tabPatterns().find(t => t.value() === value); + this._pattern.selectedTab.set(tab); + tab?.expanded.set(true); + } }); + } - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - - const values = this._collection.orderedItems().map(t => t.value()); - const duplicates = values.filter((item, index) => values.indexOf(item) !== index); - if (duplicates.length > 0) { - violations.push(`Duplicate value '${duplicates[0]}' detected inside ngTabList.`); - } - - reportViolations(violations, this.element); - }, - }); - } + _onFocus() { + this._hasFocused.set(true); } ngOnInit() { - this._tabsParent._register(this); + this._tabs._register(this); } ngOnDestroy() { - this._tabsParent._unregister(); - this._collection.stopObserving(); + this._tabs._unregister(this); } - /** Opens the tab panel with the specified value. */ - open(value: string): boolean { - return this._pattern.open(this.findTab(value)?._pattern); + _register(child: Tab) { + this._unorderedTabs().add(child); + this._unorderedTabs.set(new Set(this._unorderedTabs())); + } + + _unregister(child: Tab) { + this._unorderedTabs().delete(child); + this._unorderedTabs.set(new Set(this._unorderedTabs())); } - findTab(value?: string) { - return value ? this._collection.orderedItems().find(tab => tab.value() === value) : undefined; + /** Opens the tab panel with the specified value. */ + open(value: string): boolean { + return this._pattern.open(value); } } diff --git a/src/aria/tabs/tab-panel.ts b/src/aria/tabs/tab-panel.ts index 42eb5759240b..31f594e7443c 100644 --- a/src/aria/tabs/tab-panel.ts +++ b/src/aria/tabs/tab-panel.ts @@ -14,13 +14,11 @@ import { inject, input, afterRenderEffect, - contentChild, OnInit, OnDestroy, } from '@angular/core'; -import {TabPanelPattern, DeferredContentAware, reportViolations} from '../private'; -import {TABS} from './tab-tokens'; -import {TabContent} from './tab-content'; +import {TabPanelPattern, DeferredContentAware} from '../private'; +import {TABS} from './utils'; /** * A TabPanel container for the resources of layered content associated with a tab. @@ -37,6 +35,8 @@ import {TabContent} from './tab-content'; *
* ``` * + * @developerPreview 21.0 + * * @see [Tabs](guide/aria/tabs) */ @Directive({ @@ -73,9 +73,9 @@ export class TabPanel implements OnInit, OnDestroy { readonly id = input(inject(_IdGenerator).getId('ng-tabpanel-', true)); /** The Tab UIPattern associated with the tabpanel */ - private readonly _tabPattern = computed(() => { - return this._tabs._tabMap().get(this.value()); - }); + private readonly _tabPattern = computed(() => + this._tabs._tabPatterns()?.find(tab => tab.value() === this.value()), + ); /** A local unique identifier for the tabpanel. */ readonly value = input.required(); @@ -89,42 +89,15 @@ export class TabPanel implements OnInit, OnDestroy { tab: this._tabPattern, }); - private readonly _tabContent = contentChild(TabContent); - constructor() { - // Connect the panel's hidden state to the DeferredContentAware's visibility. - afterRenderEffect({ - write: () => { - this._deferredContentAware.contentVisible.set(this.visible()); - }, - }); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - - if (!this._tabContent()) { - violations.push('ngTabPanel must have an ngTabContent structural directive to render.'); - } - if (!this._tabs._tabMap().has(this.value())) { - violations.push( - `ngTabPanel with value '${this.value()}' does not have a corresponding ngTab.`, - ); - } - - reportViolations(violations, this.element); - }, - }); - } + afterRenderEffect(() => this._deferredContentAware.contentVisible.set(this.visible())); } ngOnInit() { - this._tabs._collection.register(this); + this._tabs._register(this); } ngOnDestroy() { - this._tabs._collection.unregister(this); + this._tabs._unregister(this); } } diff --git a/src/aria/tabs/tab.ts b/src/aria/tabs/tab.ts index 8fdaaf1f83bd..fcc6c07a5763 100644 --- a/src/aria/tabs/tab.ts +++ b/src/aria/tabs/tab.ts @@ -8,18 +8,19 @@ import {_IdGenerator} from '@angular/cdk/a11y'; import { - Directive, - ElementRef, - OnInit, - OnDestroy, booleanAttribute, computed, + Directive, + ElementRef, inject, input, - afterRenderEffect, + signal, + OnInit, + OnDestroy, } from '@angular/core'; -import {TabPattern, HasElement, reportViolations} from '../private'; -import {TAB_LIST} from './tab-tokens'; +import {TabPattern} from '../private'; +import {TabList} from './tab-list'; +import {HasElement, TABS} from './utils'; /** * A selectable tab in a TabList. @@ -33,6 +34,8 @@ import {TAB_LIST} from './tab-tokens'; * * ``` * + * @developerPreview 21.0 + * * @see [Tabs](guide/aria/tabs) */ @Directive({ @@ -55,16 +58,22 @@ export class Tab implements HasElement, OnInit, OnDestroy { /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent Tabs. */ + private readonly _tabs = inject(TABS); + /** The parent TabList. */ - private readonly _tabList = inject(TAB_LIST); + private readonly _tabList = inject(TabList); /** A unique identifier for the widget. */ readonly id = input(inject(_IdGenerator).getId('ng-tab-', true)); + /** The parent TabList UIPattern. */ + private readonly _tablistPattern = computed(() => this._tabList._pattern); + /** The TabPanel UIPattern associated with the tab */ - private readonly _tabpanelPattern = computed(() => { - return this._tabList._tabsParent._panelMap().get(this.value()); - }); + private readonly _tabpanelPattern = computed(() => + this._tabs._unorderedTabpanelPatterns().find(tabpanel => tabpanel.value() === this.value()), + ); /** Whether a tab is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); @@ -81,9 +90,10 @@ export class Tab implements HasElement, OnInit, OnDestroy { /** The Tab UIPattern. */ readonly _pattern: TabPattern = new TabPattern({ ...this, + tablist: this._tablistPattern, + tabpanel: this._tabpanelPattern, + expanded: signal(false), element: () => this.element, - tabList: () => this._tabList._pattern, - tabPanel: this._tabpanelPattern, }); /** Opens this tab panel. */ @@ -91,34 +101,11 @@ export class Tab implements HasElement, OnInit, OnDestroy { this._pattern.open(); } - constructor() { - // Automatically prevent form submission. - if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { - this.element.setAttribute('type', 'button'); - } - - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - if (this._tabList && this._tabList._tabsParent) { - if (!this._tabList._tabsParent._panelMap().has(this.value())) { - violations.push( - `ngTab with value '${this.value()}' does not have a corresponding ngTabPanel.`, - ); - } - } - reportViolations(violations, this.element); - }, - }); - } - } - ngOnInit() { - this._tabList._collection.register(this); + this._tabList._register(this); } ngOnDestroy() { - this._tabList._collection.unregister(this); + this._tabList._unregister(this); } } diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index 065677e040d1..8ac9ed40301d 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -1,9 +1,8 @@ -import {ChangeDetectionStrategy, Component, DebugElement, signal} from '@angular/core'; +import {Component, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; import {Tabs} from './tabs'; import {TabList} from './tab-list'; import {Tab} from './tab'; @@ -28,15 +27,17 @@ describe('Tabs', () => { let fixture: ComponentFixture; let testComponent: TestTabsComponent; + let tabsDebugElement: DebugElement; let tabListDebugElement: DebugElement; let tabDebugElements: DebugElement[]; let tabPanelDebugElements: DebugElement[]; + let tabsElement: HTMLElement; let tabListElement: HTMLElement; let tabElements: HTMLElement[]; let tabPanelElements: HTMLElement[]; - const keydown = async (key: string, modifierKeys: ModifierKeys = {}) => { + const keydown = (key: string, modifierKeys: ModifierKeys = {}) => { tabListElement.dispatchEvent( new KeyboardEvent('keydown', { key, @@ -44,31 +45,26 @@ describe('Tabs', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); }; - const click = async (target: HTMLElement, eventInit?: PointerEventInit) => { - target.dispatchEvent( - new PointerEvent('click', { - bubbles: true, - ...eventInit, - }), - ); - await fixture.whenStable(); + const pointerDown = (target: HTMLElement, eventInit?: PointerEventInit) => { + target.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, ...eventInit})); + fixture.detectChanges(); defineTestVariables(); }; - const space = async (modifierKeys?: ModifierKeys) => await keydown(' ', modifierKeys); - const enter = async (modifierKeys?: ModifierKeys) => await keydown('Enter', modifierKeys); - const up = async (modifierKeys?: ModifierKeys) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: ModifierKeys) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: ModifierKeys) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: ModifierKeys) => await keydown('ArrowRight', modifierKeys); - const home = async (modifierKeys?: ModifierKeys) => await keydown('Home', modifierKeys); - const end = async (modifierKeys?: ModifierKeys) => await keydown('End', modifierKeys); + const space = (modifierKeys?: ModifierKeys) => keydown(' ', modifierKeys); + const enter = (modifierKeys?: ModifierKeys) => keydown('Enter', modifierKeys); + const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys); + const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys); + const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys); + const right = (modifierKeys?: ModifierKeys) => keydown('ArrowRight', modifierKeys); + const home = (modifierKeys?: ModifierKeys) => keydown('Home', modifierKeys); + const end = (modifierKeys?: ModifierKeys) => keydown('End', modifierKeys); - async function setupTestTabs(options: {textDirection?: Direction} = {}) { + function setupTestTabs(options: {textDirection?: Direction} = {}) { TestBed.configureTestingModule({ providers: [provideFakeDirectionality(options.textDirection ?? 'ltr')], }); @@ -76,11 +72,11 @@ describe('Tabs', () => { fixture = TestBed.createComponent(TestTabsComponent); testComponent = fixture.componentInstance; - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } - async function updateTabs( + function updateTabs( options: { initialTabs?: TestTabDefinition[]; selectedTab?: string | undefined; @@ -101,15 +97,17 @@ describe('Tabs', () => { if (options.focusMode !== undefined) testComponent.focusMode.set(options.focusMode); if (options.selectionMode !== undefined) testComponent.selectionMode.set(options.selectionMode); - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } function defineTestVariables() { + tabsDebugElement = fixture.debugElement.query(By.directive(Tabs)); tabListDebugElement = fixture.debugElement.query(By.directive(TabList)); tabDebugElements = fixture.debugElement.queryAll(By.directive(Tab)); tabPanelDebugElements = fixture.debugElement.queryAll(By.directive(TabPanel)); + tabsElement = tabsDebugElement.nativeElement; tabListElement = tabListDebugElement.nativeElement; tabElements = tabDebugElements.map(debugEl => debugEl.nativeElement); tabPanelElements = tabPanelDebugElements.map(debugEl => debugEl.nativeElement); @@ -124,15 +122,15 @@ describe('Tabs', () => { } afterEach(async () => { - if (fixture.nativeElement) { - await runAccessibilityChecks(fixture.nativeElement); + if (tabsElement) { + await runAccessibilityChecks(tabsElement); } }); describe('ARIA attributes and roles', () => { - beforeEach(async () => { - await setupTestTabs(); - await updateTabs({ + beforeEach(() => { + setupTestTabs(); + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2', disabled: true}, @@ -146,33 +144,33 @@ describe('Tabs', () => { expect(tabListElement.getAttribute('role')).toBe('tablist'); }); - it('should set aria-orientation based on input', async () => { + it('should set aria-orientation based on input', () => { expect(tabListElement.getAttribute('aria-orientation')).toBe('horizontal'); - await updateTabs({orientation: 'vertical'}); + updateTabs({orientation: 'vertical'}); expect(tabListElement.getAttribute('aria-orientation')).toBe('vertical'); }); - it('should set aria-disabled based on input', async () => { + it('should set aria-disabled based on input', () => { expect(tabListElement.getAttribute('aria-disabled')).toBe('false'); - await updateTabs({disabled: true}); + updateTabs({disabled: true}); expect(tabListElement.getAttribute('aria-disabled')).toBe('true'); }); - it('should have tabindex set by focusMode', async () => { - await updateTabs({focusMode: 'roving'}); + it('should have tabindex set by focusMode', () => { + updateTabs({focusMode: 'roving'}); expect(tabListElement.getAttribute('tabindex')).toBe('-1'); - await updateTabs({focusMode: 'activedescendant'}); + updateTabs({focusMode: 'activedescendant'}); expect(tabListElement.getAttribute('tabindex')).toBe('0'); }); - it('should set aria-activedescendant in activedescendant mode', async () => { - await updateTabs({focusMode: 'activedescendant', selectedTab: 'tab1'}); + it('should set aria-activedescendant in activedescendant mode', () => { + updateTabs({focusMode: 'activedescendant', selectedTab: 'tab1'}); expect(tabListElement.getAttribute('aria-activedescendant')).toBe(tabElements[0].id); }); - it('should not set aria-activedescendant in roving mode', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should not set aria-activedescendant in roving mode', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabListElement.hasAttribute('aria-activedescendant')).toBe(false); }); }); @@ -184,13 +182,13 @@ describe('Tabs', () => { }); }); - it('should have aria-selected based on selection state', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should have aria-selected based on selection state', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); expect(tabElements[2].getAttribute('aria-selected')).toBe('false'); - await updateTabs({selectedTab: 'tab3'}); + updateTabs({selectedTab: 'tab3'}); expect(tabElements[0].getAttribute('aria-selected')).toBe('false'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); expect(tabElements[2].getAttribute('aria-selected')).toBe('true'); @@ -208,13 +206,13 @@ describe('Tabs', () => { expect(tabElements[2].getAttribute('aria-disabled')).toBe('false'); }); - it('should have tabindex set by focusMode and active state', async () => { - await updateTabs({focusMode: 'roving', selectedTab: 'tab1'}); + it('should have tabindex set by focusMode and active state', () => { + updateTabs({focusMode: 'roving', selectedTab: 'tab1'}); expect(tabElements[0].getAttribute('tabindex')).toBe('0'); expect(tabElements[1].getAttribute('tabindex')).toBe('-1'); expect(tabElements[2].getAttribute('tabindex')).toBe('-1'); - await updateTabs({focusMode: 'activedescendant'}); + updateTabs({focusMode: 'activedescendant'}); tabElements.forEach(tabElement => { expect(tabElement.getAttribute('tabindex')).toBe('-1'); }); @@ -228,13 +226,13 @@ describe('Tabs', () => { }); }); - it('should have tabindex="0" when visible.', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should have tabindex="0" when visible.', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabPanelElements[0].getAttribute('tabindex')).toBe('0'); }); - it('should have tabindex="-1" when hidden.', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should have tabindex="-1" when hidden.', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabPanelElements[1].getAttribute('tabindex')).toBe('-1'); expect(tabPanelElements[2].getAttribute('tabindex')).toBe('-1'); }); @@ -245,13 +243,13 @@ describe('Tabs', () => { expect(tabPanelElements[2].getAttribute('aria-labelledby')).toBe(tabElements[2].id); }); - it('should have inert attribute when hidden and not when visible', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should have inert attribute when hidden and not when visible', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabPanelElements[0].hasAttribute('inert')).toBe(false); expect(tabPanelElements[1].hasAttribute('inert')).toBe(true); expect(tabPanelElements[2].hasAttribute('inert')).toBe(true); - await updateTabs({selectedTab: 'tab3'}); + updateTabs({selectedTab: 'tab3'}); expect(tabPanelElements[0].hasAttribute('inert')).toBe(true); expect(tabPanelElements[1].hasAttribute('inert')).toBe(true); expect(tabPanelElements[2].hasAttribute('inert')).toBe(false); @@ -263,9 +261,9 @@ describe('Tabs', () => { for (const focusMode of ['roving', 'activedescendant'] as const) { describe(`focusMode="${focusMode}"`, () => { describe('LTR', () => { - beforeEach(async () => { - await setupTestTabs({textDirection: 'ltr'}); - await updateTabs({ + beforeEach(() => { + setupTestTabs({textDirection: 'ltr'}); + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2', disabled: true}, @@ -276,87 +274,87 @@ describe('Tabs', () => { }); }); - it('should move focus with ArrowRight', async () => { + it('should move focus with ArrowRight', () => { expect(isTabFocused(0)).toBe(true); - await right(); + right(); expect(isTabFocused(1)).toBe(true); }); - it('should move focus with ArrowLeft', async () => { - await right(); + it('should move focus with ArrowLeft', () => { + right(); expect(isTabFocused(1)).toBe(true); - await left(); + left(); expect(isTabFocused(0)).toBe(true); }); - it('should wrap focus with ArrowRight if wrap is true', async () => { - await updateTabs({wrap: true}); - await right(); + it('should wrap focus with ArrowRight if wrap is true', () => { + updateTabs({wrap: true}); + right(); expect(isTabFocused(1)).toBe(true); - await right(); + right(); expect(isTabFocused(2)).toBe(true); - await right(); + right(); expect(isTabFocused(0)).toBe(true); }); - it('should not wrap focus with ArrowRight if wrap is false', async () => { - await updateTabs({wrap: false}); - await right(); + it('should not wrap focus with ArrowRight if wrap is false', () => { + updateTabs({wrap: false}); + right(); expect(isTabFocused(1)).toBe(true); - await right(); + right(); expect(isTabFocused(2)).toBe(true); - await right(); + right(); expect(isTabFocused(2)).toBe(true); }); - it('should wrap focus with ArrowLeft if wrap is true', async () => { - await updateTabs({wrap: true}); + it('should wrap focus with ArrowLeft if wrap is true', () => { + updateTabs({wrap: true}); expect(isTabFocused(0)).toBe(true); - await left(); + left(); expect(isTabFocused(2)).toBe(true); }); - it('should not wrap focus with ArrowLeft if wrap is false', async () => { - await updateTabs({wrap: false}); + it('should not wrap focus with ArrowLeft if wrap is false', () => { + updateTabs({wrap: false}); expect(isTabFocused(0)).toBe(true); - await left(); + left(); expect(isTabFocused(0)).toBe(true); }); - it('should move focus to first tab with Home', async () => { - await left(); + it('should move focus to first tab with Home', () => { + left(); expect(isTabFocused(2)).toBe(true); - await home(); + home(); expect(isTabFocused(0)).toBe(true); }); - it('should move focus to last tab with End', async () => { + it('should move focus to last tab with End', () => { expect(isTabFocused(0)).toBe(true); - await end(); + end(); expect(isTabFocused(2)).toBe(true); }); - it('should skip disabled tabs if softDisabled is false', async () => { - await updateTabs({softDisabled: false}); + it('should skip disabled tabs if softDisabled is false', () => { + updateTabs({softDisabled: false}); expect(isTabFocused(0)).toBe(true); - await right(); + right(); expect(isTabFocused(2)).toBe(true); }); - it('should not skip disabled tabs if softDisabled is true', async () => { - await updateTabs({softDisabled: true}); + it('should not skip disabled tabs if softDisabled is true', () => { + updateTabs({softDisabled: true}); tabListElement.focus(); - await fixture.whenStable(); + fixture.detectChanges(); expect(isTabFocused(0)).toBe(true); - await right(); + right(); expect(isTabFocused(1)).toBe(true); }); }); describe('RTL', () => { - beforeEach(async () => { - await setupTestTabs({textDirection: 'rtl'}); - await updateTabs({ + beforeEach(() => { + setupTestTabs({textDirection: 'rtl'}); + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2', disabled: true}, @@ -366,41 +364,41 @@ describe('Tabs', () => { selectedTab: 'tab1', }); }); - it('should move focus with ArrowLeft (effectively next)', async () => { + it('should move focus with ArrowLeft (effectively next)', () => { expect(isTabFocused(0)).toBe(true); - await left(); + left(); expect(isTabFocused(1)).toBe(true); }); - it('should move focus with ArrowRight (effectively previous)', async () => { - await left(); + it('should move focus with ArrowRight (effectively previous)', () => { + left(); expect(isTabFocused(1)).toBe(true); - await right(); + right(); expect(isTabFocused(0)).toBe(true); }); - it('should wrap focus with ArrowLeft if wrap is true', async () => { - await updateTabs({wrap: true}); - await left(); + it('should wrap focus with ArrowLeft if wrap is true', () => { + updateTabs({wrap: true}); + left(); expect(isTabFocused(1)).toBe(true); - await left(); + left(); expect(isTabFocused(2)).toBe(true); - await left(); + left(); expect(isTabFocused(0)).toBe(true); }); - it('should not wrap focus with ArrowLeft if wrap is false', async () => { - await updateTabs({wrap: false}); - await left(); - await left(); + it('should not wrap focus with ArrowLeft if wrap is false', () => { + updateTabs({wrap: false}); + left(); + left(); expect(isTabFocused(2)).toBe(true); }); }); describe('orientation="vertical"', () => { - beforeEach(async () => { - await setupTestTabs({textDirection: 'ltr'}); - await updateTabs({ + beforeEach(() => { + setupTestTabs({textDirection: 'ltr'}); + updateTabs({ orientation: 'vertical', initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, @@ -412,71 +410,71 @@ describe('Tabs', () => { }); }); - it('should move focus with ArrowDown', async () => { + it('should move focus with ArrowDown', () => { expect(isTabFocused(0)).toBe(true); - await down(); + down(); expect(isTabFocused(1)).toBe(true); }); - it('should move focus with ArrowUp', async () => { - await down(); + it('should move focus with ArrowUp', () => { + down(); expect(isTabFocused(1)).toBe(true); - await up(); + up(); expect(isTabFocused(0)).toBe(true); }); - it('should wrap focus with ArrowDown if wrap is true', async () => { - await updateTabs({wrap: true}); - await down(); + it('should wrap focus with ArrowDown if wrap is true', () => { + updateTabs({wrap: true}); + down(); expect(isTabFocused(1)).toBe(true); - await down(); + down(); expect(isTabFocused(2)).toBe(true); - await down(); + down(); expect(isTabFocused(0)).toBe(true); }); - it('should not wrap focus with ArrowDown if wrap is false', async () => { - await updateTabs({wrap: false}); - await down(); + it('should not wrap focus with ArrowDown if wrap is false', () => { + updateTabs({wrap: false}); + down(); expect(isTabFocused(1)).toBe(true); - await down(); + down(); expect(isTabFocused(2)).toBe(true); - await down(); + down(); expect(isTabFocused(2)).toBe(true); }); - it('should wrap focus with ArrowUp if wrap is true', async () => { - await updateTabs({wrap: true}); + it('should wrap focus with ArrowUp if wrap is true', () => { + updateTabs({wrap: true}); expect(isTabFocused(0)).toBe(true); - await up(); + up(); expect(isTabFocused(2)).toBe(true); }); - it('should not wrap focus with ArrowUp if wrap is false', async () => { - await updateTabs({wrap: false}); + it('should not wrap focus with ArrowUp if wrap is false', () => { + updateTabs({wrap: false}); expect(isTabFocused(0)).toBe(true); - await up(); + up(); expect(isTabFocused(0)).toBe(true); }); - it('should move focus to first tab with Home', async () => { - await end(); + it('should move focus to first tab with Home', () => { + end(); expect(isTabFocused(2)).toBe(true); - await home(); + home(); expect(isTabFocused(0)).toBe(true); }); - it('should move focus to last tab with End', async () => { + it('should move focus to last tab with End', () => { expect(isTabFocused(0)).toBe(true); - await end(); + end(); expect(isTabFocused(2)).toBe(true); }); - it('should not move focus with ArrowLeft/ArrowRight', async () => { + it('should not move focus with ArrowLeft/ArrowRight', () => { expect(isTabFocused(0)).toBe(true); - await left(); + left(); expect(isTabFocused(0)).toBe(true); - await right(); + right(); expect(isTabFocused(0)).toBe(true); }); }); @@ -484,41 +482,10 @@ describe('Tabs', () => { } }); - describe('with shuffled items', () => { - it('should update collection order when items are shuffled', async () => { - await setupTestTabs(); - await updateTabs({ - initialTabs: [ - {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, - {value: 'tab2', label: 'Tab 2', content: 'Content 2'}, - {value: 'tab3', label: 'Tab 3', content: 'Content 3'}, - ], - }); - - // Verify initial DOM order - expect(tabElements.length).toBe(3); - expect(tabElements[0].textContent?.trim()).toBe('Tab 1'); - expect(tabElements[2].textContent?.trim()).toBe('Tab 3'); - - // Shuffle (reverse) data - const items = testComponent.tabsData().reverse(); - testComponent.tabsData.set([...items]); - await fixture.whenStable(); - await waitForMicrotasks(); - - // Re-query elements to check new DOM order - defineTestVariables(); - - expect(tabElements.length).toBe(3); - expect(tabElements[0].textContent?.trim()).toBe('Tab 3'); - expect(tabElements[2].textContent?.trim()).toBe('Tab 1'); - }); - }); - describe('Tab selection', () => { - beforeEach(async () => { - await setupTestTabs(); - await updateTabs({ + beforeEach(() => { + setupTestTabs(); + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2'}, @@ -529,147 +496,147 @@ describe('Tabs', () => { }); describe('selectionMode="follow"', () => { - beforeEach(async () => { - await updateTabs({selectionMode: 'follow'}); + beforeEach(() => { + updateTabs({selectionMode: 'follow'}); }); - it('should select tab on focus via ArrowKeys', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should select tab on focus via ArrowKeys', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); - await right(); + right(); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); expect(tabElements[0].getAttribute('aria-selected')).toBe('false'); - await left(); + left(); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); }); - it('should select tab on focus via Home/End', async () => { - await updateTabs({selectedTab: 'tab2'}); + it('should select tab on focus via Home/End', () => { + updateTabs({selectedTab: 'tab2'}); expect(testComponent.selectedTab()).toBe('tab2'); - await home(); + home(); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); - await end(); + end(); expect(testComponent.selectedTab()).toBe('tab3'); expect(tabElements[2].getAttribute('aria-selected')).toBe('true'); }); - it('should select tab on click', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should select tab on click', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); - await click(tabElements[1]); + pointerDown(tabElements[1]); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should not change selection with Space/Enter on already selected tab', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should not change selection with Space/Enter on already selected tab', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); - await space(); + space(); expect(testComponent.selectedTab()).toBe('tab1'); - await enter(); + enter(); expect(testComponent.selectedTab()).toBe('tab1'); }); }); describe('selectionMode="explicit"', () => { - beforeEach(async () => { - await updateTabs({selectionMode: 'explicit'}); + beforeEach(() => { + updateTabs({selectionMode: 'explicit'}); }); - it('should not select tab on focus via ArrowKeys', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should not select tab on focus via ArrowKeys', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); - await right(); + right(); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); expect(isTabFocused(1)).toBe(true); - await left(); + left(); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); expect(isTabFocused(0)).toBe(true); }); - it('should select focused tab on Space', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should select focused tab on Space', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); - await right(); + right(); expect(isTabFocused(1)).toBe(true); expect(testComponent.selectedTab()).toBe('tab1'); - await space(); + space(); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should select focused tab on Enter', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should select focused tab on Enter', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); - await right(); + right(); expect(isTabFocused(1)).toBe(true); expect(testComponent.selectedTab()).toBe('tab1'); - await enter(); + enter(); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); }); - it('should select tab on click', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should select tab on click', () => { + updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1'); - await click(tabElements[1]); + pointerDown(tabElements[1]); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); }); }); - it('should update selectedTab model on selection change', async () => { - await updateTabs({selectedTab: 'tab1', selectionMode: 'follow'}); + it('should update selectedTab model on selection change', () => { + updateTabs({selectedTab: 'tab1', selectionMode: 'follow'}); expect(testComponent.selectedTab()).toBe('tab1'); - await right(); + right(); expect(testComponent.selectedTab()).toBe('tab2'); - await updateTabs({selectionMode: 'explicit'}); - await right(); + updateTabs({selectionMode: 'explicit'}); + right(); expect(testComponent.selectedTab()).toBe('tab2'); - await enter(); + enter(); expect(testComponent.selectedTab()).toBe('tab3'); - await click(tabElements[0]); + pointerDown(tabElements[0]); expect(testComponent.selectedTab()).toBe('tab1'); }); - it('should update selection when selectedTab model changes', async () => { - await updateTabs({selectedTab: 'tab1'}); + it('should update selection when selectedTab model changes', () => { + updateTabs({selectedTab: 'tab1'}); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); - await updateTabs({selectedTab: 'tab2'}); + updateTabs({selectedTab: 'tab2'}); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); expect(tabElements[0].getAttribute('aria-selected')).toBe('false'); - await updateTabs({selectedTab: 'tab3'}); + updateTabs({selectedTab: 'tab3'}); expect(tabElements[2].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); }); - it('should not select a disabled tab via click', async () => { - await updateTabs({ + it('should not select a disabled tab via click', () => { + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2', disabled: true}, @@ -678,13 +645,13 @@ describe('Tabs', () => { selectedTab: 'tab1', }); expect(testComponent.selectedTab()).toBe('tab1'); - await click(tabElements[1]); + pointerDown(tabElements[1]); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); }); - it('should not select a disabled tab via keyboard', async () => { - await updateTabs({ + it('should not select a disabled tab via keyboard', () => { + updateTabs({ initialTabs: [ {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, {value: 'tab2', label: 'Tab 2', content: 'Content 2', disabled: true}, @@ -695,31 +662,31 @@ describe('Tabs', () => { softDisabled: true, }); expect(testComponent.selectedTab()).toBe('tab1'); - await right(); + right(); expect(isTabFocused(1)).toBe(true); - await enter(); + enter(); expect(testComponent.selectedTab()).toBe('tab1'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); }); - it('should not change selection if tablist is disabled', async () => { - await updateTabs({selectedTab: 'tab1', disabled: true}); + it('should not change selection if tablist is disabled', () => { + updateTabs({selectedTab: 'tab1', disabled: true}); expect(testComponent.selectedTab()).toBe('tab1'); - await click(tabElements[1]); + pointerDown(tabElements[1]); expect(testComponent.selectedTab()).toBe('tab1'); - await right(); + right(); expect(testComponent.selectedTab()).toBe('tab1'); }); - it('should handle initial selection via input', async () => { - await updateTabs({selectedTab: 'tab2'}); + it('should handle initial selection via input', () => { + updateTabs({selectedTab: 'tab2'}); expect(testComponent.selectedTab()).toBe('tab2'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); expect(tabElements[0].getAttribute('aria-selected')).toBe('false'); }); - it('should allow programmatic selection even if disabled', async () => { - await updateTabs({disabled: true}); + it('should allow programmatic selection even if disabled', () => { + updateTabs({disabled: true}); expect(tabElements[0].getAttribute('aria-selected')).toBe('true'); expect(tabElements[1].getAttribute('aria-selected')).toBe('false'); @@ -729,7 +696,7 @@ describe('Tabs', () => { expect(tabPanelElements[1].hasAttribute('inert')).toBe(true); expect(tabPanelElements[2].hasAttribute('inert')).toBe(true); - await updateTabs({selectedTab: 'tab2'}); + updateTabs({selectedTab: 'tab2'}); expect(tabElements[0].getAttribute('aria-selected')).toBe('false'); expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); @@ -740,135 +707,6 @@ describe('Tabs', () => { expect(tabPanelElements[2].hasAttribute('inert')).toBe(true); }); }); - - describe('Dynamic tabs', () => { - beforeEach(async () => { - await setupTestTabs(); - await updateTabs({ - initialTabs: [ - {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, - {value: 'tab2', label: 'Tab 2', content: 'Content 2'}, - {value: 'tab3', label: 'Tab 3', content: 'Content 3'}, - ], - selectedTab: 'tab2', - }); - }); - - it('should update selection when active tab is removed', async () => { - expect(testComponent.selectedTab()).toBe('tab2'); - - testComponent.tabsData.set([ - {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, - {value: 'tab3', label: 'Tab 3', content: 'Content 3'}, - ]); - await fixture.whenStable(); - defineTestVariables(); - - expect(testComponent.selectedTab()).toBeUndefined(); - }); - - it('should maintain selection when a new tab is added', async () => { - expect(testComponent.selectedTab()).toBe('tab2'); - - testComponent.tabsData.set([ - {value: 'tab1', label: 'Tab 1', content: 'Content 1'}, - {value: 'tab2', label: 'Tab 2', content: 'Content 2'}, - {value: 'tab3', label: 'Tab 3', content: 'Content 3'}, - {value: 'tab4', label: 'Tab 4', content: 'Content 4'}, - ]); - await fixture.whenStable(); - defineTestVariables(); - - expect(testComponent.selectedTab()).toBe('tab2'); - }); - }); - - describe('Custom IDs', () => { - let customIdFixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [provideFakeDirectionality('ltr')], - }); - customIdFixture = TestBed.createComponent(TestTabsCustomIdComponent); - fixture = customIdFixture as any; - customIdFixture.detectChanges(); - }); - - it('should use custom ID for tab and link to panel', async () => { - const tabEl = customIdFixture.nativeElement.querySelector('#custom-tab-id'); - const panelEl = customIdFixture.nativeElement.querySelector('#custom-panel-id'); - - expect(tabEl).toBeTruthy(); - expect(panelEl).toBeTruthy(); - - expect(tabEl.getAttribute('aria-controls')).toBe('custom-panel-id'); - expect(panelEl.getAttribute('aria-labelledby')).toBe('custom-tab-id'); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupTestTabs(); - }); - - it('should warn when ngTab is missing its corresponding ngTabPanel', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [TabWithoutPanelComponent], - }); - const noPanelFixture = TestBed.createComponent(TabWithoutPanelComponent); - noPanelFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - "ngTab with value 'tab1' does not have a corresponding ngTabPanel.", - ); - }); - - it('should warn when ngTabPanel is missing its corresponding ngTab', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [PanelWithoutTabComponent], - }); - const noTabFixture = TestBed.createComponent(PanelWithoutTabComponent); - noTabFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - "ngTabPanel with value 'tab1' does not have a corresponding ngTab.", - ); - }); - - it('should warn when ngTabPanel is missing ngTabContent structural directive', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [PanelWithoutContentComponent], - }); - const noContentFixture = TestBed.createComponent(PanelWithoutContentComponent); - noContentFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngTabPanel must have an ngTabContent structural directive to render.', - ); - }); - - it('should warn when duplicate values are detected inside ngTabList', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [DuplicateTabValuesComponent], - }); - const duplicateFixture = TestBed.createComponent(DuplicateTabValuesComponent); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith("Duplicate value 'tab1' detected inside ngTabList."); - }); - }); }); @Component({ @@ -895,7 +733,6 @@ describe('Tabs', () => {
`, imports: [Tabs, TabList, Tab, TabPanel, TabContent], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestTabsComponent { tabsData = signal([ @@ -927,80 +764,3 @@ class TestTabsComponent { focusMode = signal<'roving' | 'activedescendant'>('roving'); selectionMode = signal<'follow' | 'explicit'>('follow'); } - -@Component({ - template: ` -
-
    -
  • Tab 1
  • -
-
- Content 1 -
-
- `, - imports: [Tabs, TabList, Tab, TabPanel, TabContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class TestTabsCustomIdComponent { - selectedTab = signal('tab1'); -} - -@Component({ - template: ` -
-
    -
  • Tab 1
  • -
-
- `, - imports: [Tabs, TabList, Tab], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class TabWithoutPanelComponent {} - -@Component({ - template: ` -
-
- Content 1 -
-
- `, - imports: [Tabs, TabPanel, TabContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class PanelWithoutTabComponent {} - -@Component({ - template: ` -
-
    -
  • Tab 1
  • -
-
- Content 1 -
-
- `, - imports: [Tabs, TabList, Tab, TabPanel], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class PanelWithoutContentComponent {} - -@Component({ - template: ` -
-
    -
  • Tab 1
  • -
  • Tab 1 Copy
  • -
-
- Content 1 -
-
- `, - imports: [Tabs, TabList, Tab, TabPanel, TabContent], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class DuplicateTabValuesComponent {} diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index aa9102b1d1e3..0c8d616f411e 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -6,19 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - computed, - Directive, - ElementRef, - inject, - signal, - afterNextRender, - OnDestroy, -} from '@angular/core'; +import {computed, Directive, ElementRef, inject, signal} from '@angular/core'; import {TabList} from './tab-list'; import {TabPanel} from './tab-panel'; -import {TABS} from './tab-tokens'; -import {SortedCollection, TabPanelPattern, TabPattern} from '../private'; +import {TABS} from './utils'; +import {TabPanelPattern, TabPattern} from '../private'; /** * A Tabs container. @@ -47,6 +39,8 @@ import {SortedCollection, TabPanelPattern, TabPattern} from '../private'; *
* ``` * + * @developerPreview 21.0 + * * @see [Tabs](guide/aria/tabs) */ @Directive({ @@ -54,63 +48,46 @@ import {SortedCollection, TabPanelPattern, TabPattern} from '../private'; exportAs: 'ngTabs', providers: [{provide: TABS, useExisting: Tabs}], }) -export class Tabs implements OnDestroy { +export class Tabs { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The TabList registered for this container. */ - readonly _tabList = signal(undefined); + /** The TabList nested inside of the container. */ + private readonly _tablist = signal(undefined); - /** The collection of TabPanels. */ - readonly _collection = new SortedCollection(); + /** The TabPanels nested inside of the container. */ + private readonly _unorderedPanels = signal(new Set()); /** The Tab UIPattern of the child Tabs. */ - readonly _tabPatterns = computed(() => this._tabList()?._tabPatterns()); + readonly _tabPatterns = computed(() => this._tablist()?._tabPatterns()); /** The TabPanel UIPattern of the child TabPanels. */ - readonly _tabPanelPatterns = computed(() => - this._collection.orderedItems().map(tabpanel => tabpanel._pattern), + readonly _unorderedTabpanelPatterns = computed(() => + [...this._unorderedPanels()].map(tabpanel => tabpanel._pattern), ); - /** A reactive map of tab values to their TabPanelPattern. */ - readonly _panelMap = computed(() => { - const map = new Map(); - for (const panel of this._collection.orderedItems()) { - map.set(panel.value(), panel._pattern); + _register(child: TabList | TabPanel) { + if (child instanceof TabList) { + this._tablist.set(child); } - return map; - }); - /** A reactive map of tab values to their TabPattern. */ - readonly _tabMap = computed(() => { - const map = new Map(); - const tabList = this._tabList(); - if (tabList) { - for (const tab of tabList._collection.orderedItems()) { - map.set(tab.value(), tab._pattern); - } + if (child instanceof TabPanel) { + this._unorderedPanels().add(child); + this._unorderedPanels.set(new Set(this._unorderedPanels())); } - return map; - }); - - constructor() { - afterNextRender(() => { - this._collection.startObserving(this.element); - }); - } - - ngOnDestroy() { - this._collection.stopObserving(); } - _register(child: TabList) { - this._tabList.set(child); - } + _unregister(child: TabList | TabPanel) { + if (child instanceof TabList) { + this._tablist.set(undefined); + } - _unregister() { - this._tabList.set(undefined); + if (child instanceof TabPanel) { + this._unorderedPanels().delete(child); + this._unorderedPanels.set(new Set(this._unorderedPanels())); + } } } diff --git a/src/aria/tabs/testing/BUILD.bazel b/src/aria/tabs/testing/BUILD.bazel deleted file mode 100644 index 221855a1799f..000000000000 --- a/src/aria/tabs/testing/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/common", - "//:node_modules/@angular/core", - "//src/aria/tabs", - "//src/cdk/testing", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_tests_lib"], -) diff --git a/src/aria/tabs/testing/index.ts b/src/aria/tabs/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/tabs/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/tabs/testing/public-api.ts b/src/aria/tabs/testing/public-api.ts deleted file mode 100644 index 651c177c0555..000000000000 --- a/src/aria/tabs/testing/public-api.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './tabs-harness'; -export * from './tabs-harness-filters'; diff --git a/src/aria/tabs/testing/tabs-harness-filters.ts b/src/aria/tabs/testing/tabs-harness-filters.ts deleted file mode 100644 index c257e4dda2c1..000000000000 --- a/src/aria/tabs/testing/tabs-harness-filters.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** A set of criteria that can be used to filter a list of `TabsHarness` instances. */ -export interface TabsHarnessFilters extends BaseHarnessFilters {} - -/** A set of criteria that can be used to filter a list of `TabHarness` instances. */ -export interface TabHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose title matches the given value. */ - title?: string | RegExp; - /** Only find instances that are selected. */ - selected?: boolean; - /** Only find instances that are disabled. */ - disabled?: boolean; -} diff --git a/src/aria/tabs/testing/tabs-harness.spec.ts b/src/aria/tabs/testing/tabs-harness.spec.ts deleted file mode 100644 index 8982caba43c8..000000000000 --- a/src/aria/tabs/testing/tabs-harness.spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {ComponentHarness, HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Tabs, TabList, Tab, TabPanel, TabContent} from '../../tabs'; -import {TabsHarness} from './tabs-harness'; - -class TestContentHarness extends ComponentHarness { - static hostSelector = '.test-content'; - async getText(): Promise { - return (await this.host()).text(); - } -} - -describe('TabsHarness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - fixture = TestBed.createComponent(TabsHarnessTest); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should load harness with tabs container', async () => { - await expectAsync(loader.getHarness(TabsHarness)).toBeResolved(); - }); - - it('should get tabs', async () => { - const tabs = await loader.getHarness(TabsHarness); - - const tabItems = await tabs.getTabs(); - - expect(tabItems.length).toBe(3); - }); - - it('should get tab panel content via ContentContainerComponentHarness', async () => { - const tabs = await loader.getHarness(TabsHarness); - const tabItems = await tabs.getTabs(); - - const contentHarness = await tabItems[0].getHarness(TestContentHarness); - - expect(await contentHarness.getText()).toBe('Content 1'); - }); - - it('should get selected tab', async () => { - const tabs = await loader.getHarness(TabsHarness); - - const selectedTab = await tabs.getSelectedTab(); - - expect(await selectedTab?.getTitle()).toBe('Tab 1'); - }); - - it('should switch tabs on click', async () => { - const tabs = await loader.getHarness(TabsHarness); - const tabItems = await tabs.getTabs(); - expect(await tabItems[0].isSelected()).toBe(true); - expect(await tabItems[1].isSelected()).toBe(false); - - await tabItems[1].select(); - - expect(await tabItems[0].isSelected()).toBe(false); - expect(await tabItems[1].isSelected()).toBe(true); - }); - - it('should select tab matching filters', async () => { - const tabs = await loader.getHarness(TabsHarness); - const tabItems = await tabs.getTabs(); - - expect(await tabItems[0].isSelected()).toBe(true); - expect(await tabItems[1].isSelected()).toBe(false); - - await tabs.selectTab({title: 'Tab 2'}); - - expect(await tabItems[0].isSelected()).toBe(false); - expect(await tabItems[1].isSelected()).toBe(true); - }); - - it('should check disabled state', async () => { - const tabs = await loader.getHarness(TabsHarness); - const tabItems = await tabs.getTabs(); - - expect(await tabItems[0].isDisabled()).toBe(false); - expect(await tabItems[2].isDisabled()).toBe(true); - }); - - it('should check active state', async () => { - const tabs = await loader.getHarness(TabsHarness); - const tabItems = await tabs.getTabs(); - - expect(await tabItems[0].isActive()).toBe(true); - expect(await tabItems[1].isActive()).toBe(false); - }); - - it('should filter tabs by title', async () => { - const tabs = await loader.getHarness(TabsHarness); - - const filteredTabs = await tabs.getTabs({title: 'Tab 2'}); - - expect(filteredTabs.length).toBe(1); - expect(await filteredTabs[0].getTitle()).toBe('Tab 2'); - }); - - it('should filter tabs by selected state', async () => { - const tabs = await loader.getHarness(TabsHarness); - - const filteredTabs = await tabs.getTabs({selected: true}); - - expect(filteredTabs.length).toBe(1); - expect(await filteredTabs[0].getTitle()).toBe('Tab 1'); - }); - - it('should filter tabs by disabled state', async () => { - const tabs = await loader.getHarness(TabsHarness); - - const filteredTabs = await tabs.getTabs({disabled: true}); - - expect(filteredTabs.length).toBe(1); - expect(await filteredTabs[0].getTitle()).toBe('Tab 3'); - }); -}); - -@Component({ - template: ` -
-
    -
  • Tab 1
  • -
  • Tab 2
  • -
  • Tab 3
  • -
- -
- -
Content 1
-
-
-
- Content 2 -
-
- Content 3 -
-
- `, - imports: [Tabs, TabList, Tab, TabPanel, TabContent], -}) -class TabsHarnessTest {} diff --git a/src/aria/tabs/testing/tabs-harness.ts b/src/aria/tabs/testing/tabs-harness.ts deleted file mode 100644 index b2e82c8cf8d9..000000000000 --- a/src/aria/tabs/testing/tabs-harness.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - ComponentHarness, - ContentContainerComponentHarness, - HarnessLoader, - HarnessPredicate, -} from '@angular/cdk/testing'; -import {TabsHarnessFilters, TabHarnessFilters} from './tabs-harness-filters'; - -/** Harness for interacting with an Aria tab in tests. */ -export class TabHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngTab]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a `TabHarness` - * that meets certain criteria. - * @param options Options for filtering which tab instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: TabHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(TabHarness, options) - .addOption('title', options.title, (harness, title) => - HarnessPredicate.stringMatches(harness.getTitle(), title), - ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ); - } - - /** Gets the tab's title text. */ - async getTitle(): Promise { - return (await this.host()).text(); - } - - /** Clicks the tab to select it. */ - async select(): Promise { - return (await this.host()).click(); - } - - /** Gets whether the tab is selected. */ - async isSelected(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-selected')) === 'true'; - } - - /** Gets whether the tab is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets whether the tab is active. */ - async isActive(): Promise { - const host = await this.host(); - return (await host.getAttribute('data-active')) === 'true'; - } - - protected override async getRootHarnessLoader(): Promise { - const host = await this.host(); - const controlsId = await host.getAttribute('aria-controls'); - const documentRoot = await this.documentRootLocatorFactory().rootHarnessLoader(); - return await documentRoot.getChildLoader(`[ngTabPanel][id="${controlsId}"]`); - } -} - -/** Harness for interacting with an Aria tabs container in tests. */ -export class TabsHarness extends ComponentHarness { - static hostSelector = '[ngTabs]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a `TabsHarness` - * that meets certain criteria. - * @param options Options for filtering which tabs instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: TabsHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(TabsHarness, options); - } - - /** Gets all tabs inside the tabs container. */ - async getTabs(filters: TabHarnessFilters = {}): Promise { - return await this.locatorForAll(TabHarness.with(filters))(); - } - - /** Gets the currently selected tab. */ - async getSelectedTab(): Promise { - const tabs = await this.getTabs({selected: true}); - return tabs.length > 0 ? tabs[0] : null; - } - - /** Selects a tab matching the given filters. */ - async selectTab(filters: TabHarnessFilters = {}): Promise { - const tabs = await this.getTabs(filters); - if (tabs.length === 0) { - throw new Error(`Could not find tab matching filters: ${JSON.stringify(filters)}`); - } - await tabs[0].select(); - } -} diff --git a/src/aria/tabs/tab-tokens.ts b/src/aria/tabs/utils.ts similarity index 58% rename from src/aria/tabs/tab-tokens.ts rename to src/aria/tabs/utils.ts index ee21244b165e..8a00da60c027 100644 --- a/src/aria/tabs/tab-tokens.ts +++ b/src/aria/tabs/utils.ts @@ -8,10 +8,19 @@ import {InjectionToken} from '@angular/core'; import type {Tabs} from './tabs'; -import type {TabList} from './tab-list'; /** Token used to expose the `Tabs` directive to child directives. */ export const TABS = new InjectionToken('TABS'); -/** Token used to expose the tab list. */ -export const TAB_LIST = new InjectionToken('TAB_LIST'); +export interface HasElement { + element: HTMLElement; +} + +/** + * Sort directives by their document order. + */ +export function sortDirectives(a: HasElement, b: HasElement) { + return (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_PRECEDING) > 0 + ? 1 + : -1; +} diff --git a/src/aria/toolbar/BUILD.bazel b/src/aria/toolbar/BUILD.bazel index 08d0c64347d6..7516f3def5c0 100644 --- a/src/aria/toolbar/BUILD.bazel +++ b/src/aria/toolbar/BUILD.bazel @@ -26,7 +26,6 @@ ts_project( ":toolbar", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//src/aria/private/testing", "//src/cdk/testing/private", ], ) diff --git a/src/aria/toolbar/testing/BUILD.bazel b/src/aria/toolbar/testing/BUILD.bazel deleted file mode 100644 index 9aa18a3ffd5a..000000000000 --- a/src/aria/toolbar/testing/BUILD.bazel +++ /dev/null @@ -1,39 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//src/aria/toolbar", - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/core", - "//src/aria/toolbar", - "//src/cdk/testing", - "//src/cdk/testing/private", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_tests_lib"], -) diff --git a/src/aria/toolbar/testing/index.ts b/src/aria/toolbar/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/toolbar/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/toolbar/testing/public-api.ts b/src/aria/toolbar/testing/public-api.ts deleted file mode 100644 index b9722d2d434c..000000000000 --- a/src/aria/toolbar/testing/public-api.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './toolbar-widget-harness'; -export * from './toolbar-widget-group-harness'; -export * from './toolbar-harness'; -export * from './toolbar-harness-filters'; diff --git a/src/aria/toolbar/testing/toolbar-harness-filters.ts b/src/aria/toolbar/testing/toolbar-harness-filters.ts deleted file mode 100644 index 0993e1b3f3a9..000000000000 --- a/src/aria/toolbar/testing/toolbar-harness-filters.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** A set of criteria that can be used to filter a list of Aria toolbar instances. */ -export interface ToolbarHarnessFilters extends BaseHarnessFilters {} - -/** A set of criteria that can be used to filter a list of Aria toolbar widget group instances. */ -export interface ToolbarWidgetGroupHarnessFilters extends BaseHarnessFilters {} - -/** A set of criteria that can be used to filter a list of Aria toolbar widgets. */ -export interface ToolbarWidgetHarnessFilters extends BaseHarnessFilters { - /** Text that the widget should match. */ - text?: string | RegExp; - - /** Active state that the widget should match. */ - active?: boolean; - - /** Selected state that the widget should match. */ - selected?: boolean; -} diff --git a/src/aria/toolbar/testing/toolbar-harness.spec.ts b/src/aria/toolbar/testing/toolbar-harness.spec.ts deleted file mode 100644 index daa6c5e9873a..000000000000 --- a/src/aria/toolbar/testing/toolbar-harness.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import {Component, signal} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {HarnessLoader, parallel} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Toolbar} from '../toolbar'; -import {ToolbarWidget} from '../toolbar-widget'; -import {ToolbarWidgetGroup} from '../toolbar-widget-group'; -import {ToolbarHarness} from './toolbar-harness'; -import {ToolbarWidgetGroupHarness} from './toolbar-widget-group-harness'; -import {ToolbarWidgetHarness} from './toolbar-widget-harness'; - -describe('ToolbarHarness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - fixture = TestBed.createComponent(ToolbarHarnessTest); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.documentRootLoader(fixture); - }); - - it('should be able to load toolbar harnesses', async () => { - const harnesses = await loader.getAllHarnesses(ToolbarHarness); - expect(harnesses.length).toBe(1); - }); - - it('should be able to load toolbar widget group harnesses', async () => { - const harnesses = await loader.getAllHarnesses(ToolbarWidgetGroupHarness); - expect(harnesses.length).toBe(2); - }); - - it('should be able to load toolbar widget harnesses', async () => { - const harnesses = await loader.getAllHarnesses(ToolbarWidgetHarness); - expect(harnesses.length).toBe(8); - }); - - it('should be able to get the widgets in a toolbar', async () => { - const toolbar = await loader.getHarness(ToolbarHarness); - const widgets = await toolbar.getWidgets(); - expect(widgets.length).toBe(8); - }); - - it('should be able to get the widget groups in a toolbar', async () => { - const toolbar = await loader.getHarness(ToolbarHarness); - const groups = await toolbar.getWidgetGroups(); - expect(groups.length).toBe(2); - }); - - it('should be able to get the toolbar orientation', async () => { - const toolbar = await loader.getHarness(ToolbarHarness); - expect(await toolbar.getOrientation()).toBe('horizontal'); - - fixture.componentInstance.orientation.set('vertical'); - expect(await toolbar.getOrientation()).toBe('vertical'); - }); - - it('should be able to get whether the toolbar is disabled', async () => { - const toolbar = await loader.getHarness(ToolbarHarness); - expect(await toolbar.isDisabled()).toBe(false); - - fixture.componentInstance.toolbarDisabled.set(true); - expect(await toolbar.isDisabled()).toBe(true); - }); - - it('should be able to get the widgets in a widget group', async () => { - const group = await loader.getHarness(ToolbarWidgetGroupHarness); - const widgets = await group.getWidgets(); - expect(widgets.length).toBe(3); - }); - - it('should be able to load a widget harness by text', async () => { - const harnesses = await loader.getAllHarnesses(ToolbarWidgetHarness.with({text: /^Align/})); - expect(harnesses.length).toBe(3); - expect(await parallel(() => harnesses.map(harness => harness.getText()))).toEqual([ - 'Align left', - 'Align center', - 'Align right', - ]); - }); - - it('should be able to toggle the active state of a widget', async () => { - const widget = await loader.getHarness(ToolbarWidgetHarness.with({text: 'Align left'})); - expect(await widget.isActive()).toBe(false); - - await widget.click(); - expect(await widget.isActive()).toBe(true); - }); - - it('should be able to get whether a widget is disabled', async () => { - const widget = await loader.getHarness(ToolbarWidgetHarness.with({text: 'Undo'})); - expect(await widget.isDisabled()).toBe(false); - - fixture.componentInstance.undoDisabled.set(true); - expect(await widget.isDisabled()).toBe(true); - }); - - it('should be able to get whether a widget is selected', async () => { - const widget = await loader.getHarness(ToolbarWidgetHarness.with({text: 'Undo'})); - expect(await widget.isSelected()).toBe(false); - - await widget.click(); - expect(await widget.isSelected()).toBe(true); - }); -}); - -@Component({ - template: ` -
- - - -
- - - -
- -
- - - -
-
- `, - imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], -}) -class ToolbarHarnessTest { - orientation = signal<'vertical' | 'horizontal'>('horizontal'); - toolbarDisabled = signal(false); - undoDisabled = signal(false); -} diff --git a/src/aria/toolbar/testing/toolbar-harness.ts b/src/aria/toolbar/testing/toolbar-harness.ts deleted file mode 100644 index 35655384e4f5..000000000000 --- a/src/aria/toolbar/testing/toolbar-harness.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; -import { - ToolbarHarnessFilters, - ToolbarWidgetHarnessFilters, - ToolbarWidgetGroupHarnessFilters, -} from './toolbar-harness-filters'; -import {ToolbarWidgetHarness} from './toolbar-widget-harness'; -import {ToolbarWidgetGroupHarness} from './toolbar-widget-group-harness'; - -/** Harness for interacting with an Aria toolbar in tests. */ -export class ToolbarHarness extends ComponentHarness { - static hostSelector = '[ngToolbar]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a `ToolbarHarness` - * that meets certain criteria. - * @param options Options for filtering which dialog instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: ToolbarHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(ToolbarHarness, options); - } - - /** Gets all widgets in the toolbar. */ - async getWidgets(filters: ToolbarWidgetHarnessFilters = {}): Promise { - return await this.locatorForAll(ToolbarWidgetHarness.with(filters))(); - } - - /** Gets all widget groups in the toolbar. */ - async getWidgetGroups( - filters: ToolbarWidgetGroupHarnessFilters = {}, - ): Promise { - return await this.locatorForAll(ToolbarWidgetGroupHarness.with(filters))(); - } - - /** Gets whether the toolbar is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets the orientation of the toolbar. */ - async getOrientation(): Promise<'vertical' | 'horizontal'> { - const host = await this.host(); - return (await host.getAttribute('aria-orientation')) as 'vertical' | 'horizontal'; - } -} diff --git a/src/aria/toolbar/testing/toolbar-widget-group-harness.ts b/src/aria/toolbar/testing/toolbar-widget-group-harness.ts deleted file mode 100644 index f1eaa7ee8986..000000000000 --- a/src/aria/toolbar/testing/toolbar-widget-group-harness.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; -import { - ToolbarWidgetHarnessFilters, - ToolbarWidgetGroupHarnessFilters, -} from './toolbar-harness-filters'; -import {ToolbarWidgetHarness} from './toolbar-widget-harness'; - -/** Harness for interacting with an Aria toolbar widget group in tests. */ -export class ToolbarWidgetGroupHarness extends ComponentHarness { - static hostSelector = '[ngToolbarWidgetGroup]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a `ToolbarWidgetGroupHarness` - * that meets certain criteria. - * @param options Options for filtering which dialog instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with( - options: ToolbarWidgetGroupHarnessFilters = {}, - ): HarnessPredicate { - return new HarnessPredicate(ToolbarWidgetGroupHarness, options); - } - - /** Gets all widgets in the group. */ - async getWidgets(filters: ToolbarWidgetHarnessFilters = {}): Promise { - return await this.locatorForAll(ToolbarWidgetHarness.with(filters))(); - } -} diff --git a/src/aria/toolbar/testing/toolbar-widget-harness.ts b/src/aria/toolbar/testing/toolbar-widget-harness.ts deleted file mode 100644 index 1df1745c05bc..000000000000 --- a/src/aria/toolbar/testing/toolbar-widget-harness.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ContentContainerComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; -import {ToolbarWidgetHarnessFilters} from './toolbar-harness-filters'; - -/** Harness for interacting with an Aria toolbar widget in tests. */ -export class ToolbarWidgetHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngToolbarWidget]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a `ToolbarWidgetHarness` - * that meets certain criteria. - * @param options Options for filtering which dialog instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: ToolbarWidgetHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(ToolbarWidgetHarness, options) - .addOption('text', options.text, (harness, pattern) => - HarnessPredicate.stringMatches(harness.getText(), pattern), - ) - .addOption( - 'active', - options.active, - async (harness, active) => (await harness.isActive()) === active, - ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ); - } - - /** Gets the widget's text. */ - async getText(): Promise { - return (await this.host()).text(); - } - - /** Clicks the widget. */ - async click(): Promise { - return (await this.host()).click(); - } - - /** Gets whether the widget is active. */ - async isActive(): Promise { - const host = await this.host(); - return (await host.getAttribute('data-active')) === 'true'; - } - - /** Gets whether the widget is disabled. */ - async isDisabled(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-disabled')) === 'true'; - } - - /** Gets whether the widget is selected. */ - async isSelected(): Promise { - const host = await this.host(); - return (await host.getAttribute('aria-pressed')) === 'true'; - } -} diff --git a/src/aria/toolbar/toolbar-widget-group.ts b/src/aria/toolbar/toolbar-widget-group.ts index 304d3cd21ab5..01a76ec848cd 100644 --- a/src/aria/toolbar/toolbar-widget-group.ts +++ b/src/aria/toolbar/toolbar-widget-group.ts @@ -14,17 +14,18 @@ import { input, booleanAttribute, contentChildren, - afterRenderEffect, } from '@angular/core'; -import {ToolbarWidgetPattern, ToolbarWidgetGroupPattern, reportViolations} from '../private'; +import {ToolbarWidgetPattern, ToolbarWidgetGroupPattern} from '../private'; import {Toolbar} from './toolbar'; import {ToolbarWidget} from './toolbar-widget'; -import {TOOLBAR_WIDGET_GROUP} from './toolbar-tokens'; +import {TOOLBAR_WIDGET_GROUP} from './utils'; /** * A directive that groups toolbar widgets, used for more complex widgets like radio groups * that have their own internal navigation. * + * @developerPreview 21.0 + * * @see [Toolbar](guide/aria/toolbar) */ @Directive({ @@ -63,19 +64,4 @@ export class ToolbarWidgetGroup { items: this._itemPatterns, toolbar: this._toolbarPattern, }); - - constructor() { - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - const violations: string[] = []; - if (!this._toolbar) { - violations.push('ngToolbarWidgetGroup must be placed inside an ngToolbar container.'); - } - reportViolations(violations, this.element); - }, - }); - } - } } diff --git a/src/aria/toolbar/toolbar-widget.ts b/src/aria/toolbar/toolbar-widget.ts index 4a626f3f57b4..76d632952607 100644 --- a/src/aria/toolbar/toolbar-widget.ts +++ b/src/aria/toolbar/toolbar-widget.ts @@ -24,7 +24,7 @@ import { } from '../private'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Toolbar} from './toolbar'; -import {TOOLBAR_WIDGET_GROUP} from './toolbar-tokens'; +import {TOOLBAR_WIDGET_GROUP} from './utils'; import type {ToolbarWidgetGroup} from './toolbar-widget-group'; /** @@ -40,14 +40,14 @@ import type {ToolbarWidgetGroup} from './toolbar-widget-group'; * * ``` * + * @developerPreview 21.0 + * * @see [Toolbar](guide/aria/toolbar) */ @Directive({ selector: '[ngToolbarWidget]', exportAs: 'ngToolbarWidget', host: { - // Ensure the attribute is applied even when used as a host directive. - 'ngToolbarWidget': '', '[attr.data-active]': 'active()', '[attr.tabindex]': '_pattern.tabIndex()', '[attr.inert]': 'hardDisabled() ? true : null', @@ -105,10 +105,10 @@ export class ToolbarWidget implements OnInit, OnDestroy { }); ngOnInit() { - this._toolbar._collection.register(this); + this._toolbar._register(this); } ngOnDestroy() { - this._toolbar._collection.unregister(this); + this._toolbar._unregister(this); } } diff --git a/src/aria/toolbar/toolbar.spec.ts b/src/aria/toolbar/toolbar.spec.ts index 5ffecc259df2..61e06a8915cb 100644 --- a/src/aria/toolbar/toolbar.spec.ts +++ b/src/aria/toolbar/toolbar.spec.ts @@ -1,14 +1,6 @@ -import { - Component, - DebugElement, - Directive, - inject, - signal, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, DebugElement, Directive, inject, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; import {Toolbar} from './toolbar'; import {ToolbarWidgetGroup} from './toolbar-widget-group'; @@ -18,7 +10,7 @@ describe('Toolbar', () => { let fixture: ComponentFixture; let toolbarElement: HTMLElement; - const keydown = async (key: string, target?: HTMLElement, modifierKeys: {} = {}) => { + const keydown = (key: string, target?: HTMLElement, modifierKeys: {} = {}) => { const eventTarget = target || toolbarElement; eventTarget.dispatchEvent( new KeyboardEvent('keydown', { @@ -27,31 +19,28 @@ describe('Toolbar', () => { ...modifierKeys, }), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const click = async (element: HTMLElement, eventInit?: PointerEventInit) => { + const click = (element: HTMLElement, eventInit?: PointerEventInit) => { element.dispatchEvent( // Include pointerType to better simulate a real mouse click v.s. enter keyboard event. new PointerEvent('click', {bubbles: true, pointerType: 'mouse', ...eventInit}), ); - await fixture.whenStable(); + fixture.detectChanges(); }; - const right = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('ArrowRight', target, modifierKeys); - const left = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('ArrowLeft', target, modifierKeys); - const up = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('ArrowUp', target, modifierKeys); - const down = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('ArrowDown', target, modifierKeys); - const home = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('Home', target, modifierKeys); - const end = async (target?: HTMLElement, modifierKeys?: {}) => - await keydown('End', target, modifierKeys); - - async function setupToolbar( + const right = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowRight', target, modifierKeys); + const left = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowLeft', target, modifierKeys); + const up = (target?: HTMLElement, modifierKeys?: {}) => keydown('ArrowUp', target, modifierKeys); + const down = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowDown', target, modifierKeys); + const home = (target?: HTMLElement, modifierKeys?: {}) => keydown('Home', target, modifierKeys); + const end = (target?: HTMLElement, modifierKeys?: {}) => keydown('End', target, modifierKeys); + + function setupToolbar( opts: { orientation?: 'vertical' | 'horizontal'; softDisabled?: boolean; @@ -80,7 +69,7 @@ describe('Toolbar', () => { testComponent.wrap.set(opts.wrap); } - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } @@ -102,321 +91,294 @@ describe('Toolbar', () => { afterEach(async () => await runAccessibilityChecks(fixture.nativeElement)); - describe('dynamic updates', () => { - it('should update widget order correctly after widgets are shuffled', async () => { - TestBed.configureTestingModule({imports: [ShuffledToolbarExample]}); - fixture = TestBed.createComponent( - ShuffledToolbarExample, - ) as unknown as ComponentFixture; - await fixture.whenStable(); - const shuffledToolbarDebugEl = fixture.debugElement.query(By.directive(Toolbar)); - const shuffledToolbarInstance = shuffledToolbarDebugEl.injector.get(Toolbar); - - const widgetsBefore = shuffledToolbarInstance._itemPatterns(); - expect(widgetsBefore.length).toBe(3); - expect(widgetsBefore[0].element()?.textContent?.trim()).toBe('item 0'); - - const items = (fixture.componentInstance as unknown as ShuffledToolbarExample).items(); - const firstItem = items.shift()!; - items.push(firstItem); - (fixture.componentInstance as unknown as ShuffledToolbarExample).items.set([...items]); - await fixture.whenStable(); - await waitForMicrotasks(); - - const widgetsAfter = shuffledToolbarInstance._itemPatterns(); - expect(widgetsAfter.length).toBe(3); - expect(widgetsAfter[0].element()?.textContent?.trim()).toBe('item 1'); - }); - }); - describe('Navigation', () => { describe('with horizontal orientation', () => { - it('should navigate on click (horizontal)', async () => { - await setupToolbar(); + it('should navigate on click', () => { + setupToolbar(); const item3 = getWidgetEl('item 3')!; - await click(item3); + click(item3); expect(document.activeElement).toBe(item3); }); describe('with ltr text direction', () => { - beforeEach(async () => await setupToolbar()); + beforeEach(() => setupToolbar()); - it('should navigate next on ArrowRight', async () => { + it('should navigate next on ArrowRight', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await right(); + click(item0); + right(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should navigate prev on ArrowLeft', async () => { + it('should navigate prev on ArrowLeft', () => { const item1 = getWidgetEl('item 1')!; - await click(item1); - await left(); + click(item1); + left(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should not navigate next on ArrowDown when not in a widget group (horizontal, ltr)', async () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await down(); + click(item0); + down(); expect(document.activeElement).toBe(item0); }); - it('should not navigate prev on ArrowUp when not in a widget group (horizontal, ltr)', async () => { + it('should not navigate prev on ArrowUp when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await up(); + click(item0); + up(); expect(document.activeElement).toBe(item0); }); - it('should navigate next in a widget group on ArrowDown (horizontal, ltr)', async () => { + it('should navigate next in a widget group on ArrowDown', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await down(); + click(item2); + down(); expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should navigate prev in a widget group on ArrowUp (horizontal, ltr)', async () => { + it('should navigate prev in a widget group on ArrowUp', () => { const item3 = getWidgetEl('item 3')!; - await click(item3); - await up(); + click(item3); + up(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should navigate last to first in a widget group on ArrowDown (horizontal, ltr)', async () => { + it('should navigate last to first in a widget group on ArrowDown', () => { const item4 = getWidgetEl('item 4')!; - await click(item4); - await down(); + click(item4); + down(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should navigate first to last in a widget group on ArrowUp (horizontal, ltr)', async () => { + it('should navigate first to last in a widget group on ArrowUp', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await up(); + click(item2); + up(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); describe('with wrap false', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.wrap.set(false); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should not wrap from last to first', async () => { + it('should not wrap from last to first', () => { const item5 = getWidgetEl('item 5')!; - await click(item5); - await right(); + click(item5); + right(); expect(document.activeElement).toBe(item5); }); - it('should not wrap from first to last', async () => { + it('should not wrap from first to last', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(item0); }); }); describe('with softDisabled true', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.softDisabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should not skip disabled items when navigating next', async () => { + it('should not skip disabled items when navigating next', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); - await click(getWidgetEl('item 0')!); - await right(); + fixture.detectChanges(); + click(getWidgetEl('item 0')!); + right(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should not skip disabled items when navigating prev', async () => { + it('should not skip disabled items when navigating prev', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item2 = getWidgetEl('item 2')!; - await click(item2); - await left(); + click(item2); + left(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should not skip disabled groups when navigating next', async () => { + it('should not skip disabled groups when navigating next', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item1 = getWidgetEl('item 1')!; - await click(item1); - await right(); + click(item1); + right(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should not skip disabled groups when navigating prev', async () => { + it('should not skip disabled groups when navigating prev', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item5 = getWidgetEl('item 5')!; - await click(item5); - await left(); + click(item5); + left(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); - it('should navigate to the last item on End', async () => { + it('should navigate to the last item on End', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await end(); + click(item0); + end(); expect(document.activeElement).toBe(getWidgetEl('item 5')); }); - it('should navigate to the first item on Home', async () => { + it('should navigate to the first item on Home', () => { const item5 = getWidgetEl('item 5')!; - await click(item5); - await home(); + click(item5); + home(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); describe('with wrap true', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.wrap.set(true); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should wrap from last to first', async () => { + it('should wrap from last to first', () => { const item5 = getWidgetEl('item 5')!; - await click(item5); - await right(); + click(item5); + right(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should wrap from first to last', async () => { + it('should wrap from first to last', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(getWidgetEl('item 5')); }); }); }); describe('with softDisabled false', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.softDisabled.set(false); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should not navigate to disabled items on click', async () => { + it('should not navigate to disabled items on click', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item1 = getWidgetEl('item 1')!; - await click(item1); + click(item1); expect(document.activeElement).not.toBe(item1); }); - it('should skip disabled items when navigating next', async () => { + it('should skip disabled items when navigating next', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item0 = getWidgetEl('item 0')!; - await click(item0); - await right(); + click(item0); + right(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should skip disabled items when navigating prev', async () => { + it('should skip disabled items when navigating prev', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item2 = getWidgetEl('item 2')!; - await click(item2); - await left(); + click(item2); + left(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should not navigate to items in disabled groups on click', async () => { + it('should not navigate to items in disabled groups on click', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item3 = getWidgetEl('item 3')!; - await click(item3); + click(item3); expect(document.activeElement).not.toBe(item3); }); - it('should skip disabled groups when navigating next', async () => { + it('should skip disabled groups when navigating next', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item1 = getWidgetEl('item 1')!; - await click(item1); - await right(); + click(item1); + right(); expect(document.activeElement).toBe(getWidgetEl('item 5')); }); - it('should skip disabled groups when navigating prev', async () => { + it('should skip disabled groups when navigating prev', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item5 = getWidgetEl('item 5')!; - await click(item5); - await left(); + click(item5); + left(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should navigate to the last focusable item on End', async () => { + it('should navigate to the last focusable item on End', () => { fixture.componentInstance.widgets[5].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item0 = getWidgetEl('item 0')!; - await click(item0); - await end(); + click(item0); + end(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); - it('should navigate to the first focusable item on Home', async () => { + it('should navigate to the first focusable item on Home', () => { fixture.componentInstance.widgets[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item5 = getWidgetEl('item 5')!; - await click(item5); - await home(); + click(item5); + home(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); describe('with wrap true', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.wrap.set(true); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should wrap from last to first focusable item', async () => { + it('should wrap from last to first focusable item', () => { fixture.componentInstance.widgets[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item5 = getWidgetEl('item 5')!; - await click(item5); - await right(); + click(item5); + right(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should wrap from first to last focusable item', async () => { + it('should wrap from first to last focusable item', () => { fixture.componentInstance.widgets[5].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); }); describe('with wrap false', () => { - beforeEach(async () => { + beforeEach(() => { fixture.componentInstance.wrap.set(false); - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should not wrap from last to first focusable item', async () => { + it('should not wrap from last to first focusable item', () => { fixture.componentInstance.widgets[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item5 = getWidgetEl('item 5')!; - await click(item5); - await right(); + click(item5); + right(); expect(document.activeElement).toBe(item5); }); - it('should not wrap from first to last focusable item', async () => { + it('should not wrap from first to last focusable item', () => { fixture.componentInstance.widgets[5].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(item0); }); }); @@ -424,325 +386,239 @@ describe('Toolbar', () => { }); describe('with rtl text direction', () => { - beforeEach(async () => await setupToolbar({textDirection: 'rtl'})); + beforeEach(() => setupToolbar({textDirection: 'rtl'})); - it('should navigate on click (horizontal, rtl)', async () => { + it('should navigate on click', () => { const item3 = getWidgetEl('item 3')!; - await click(item3); + click(item3); expect(document.activeElement).toBe(item3); }); - it('should navigate next on ArrowLeft', async () => { + it('should navigate next on ArrowLeft', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should navigate prev on ArrowRight', async () => { - await click(getWidgetEl('item 1')!); - await right(); + it('should navigate prev on ArrowRight', () => { + click(getWidgetEl('item 1')!); + right(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should not navigate next on ArrowDown when not in a widget group (horizontal, rtl)', async () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await down(); + click(item0); + down(); expect(document.activeElement).toBe(item0); }); - it('should not navigate prev on ArrowUp when not in a widget group (horizontal, rtl)', async () => { + it('should not navigate prev on ArrowUp when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await up(); + click(item0); + up(); expect(document.activeElement).toBe(item0); }); - it('should navigate next in a widget group on ArrowDown (horizontal, rtl)', async () => { + it('should navigate next in a widget group on ArrowDown', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await down(); + click(item2); + down(); expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should navigate prev in a widget group on ArrowUp (horizontal, rtl)', async () => { + it('should navigate prev in a widget group on ArrowUp', () => { const item3 = getWidgetEl('item 3')!; - await click(item3); - await up(); + click(item3); + up(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should navigate first to last in a widget group on ArrowUp (horizontal, rtl)', async () => { + it('should navigate first to last in a widget group on ArrowUp', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await up(); + click(item2); + up(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); - it('should navigate last to first in a widget group on ArrowDown (horizontal, rtl)', async () => { + it('should navigate last to first in a widget group on ArrowDown', () => { const item4 = getWidgetEl('item 4')!; - await click(item4); - await down(); + click(item4); + down(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); }); }); describe('with vertical orientation', () => { - beforeEach(async () => await setupToolbar({orientation: 'vertical'})); + beforeEach(() => setupToolbar({orientation: 'vertical'})); - it('should navigate next on ArrowDown', async () => { + it('should navigate next on ArrowDown', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await down(); + click(item0); + down(); expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should navigate prev on ArrowUp', async () => { + it('should navigate prev on ArrowUp', () => { const item1 = getWidgetEl('item 1')!; - await click(item1); - await up(); + click(item1); + up(); expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should not navigate next on ArrowRight when not in a widget group', async () => { + it('should not navigate next on ArrowRight when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await right(); + click(item0); + right(); expect(document.activeElement).toBe(item0); }); - it('should not navigate prev on ArrowLeft when not in a widget group', async () => { + it('should not navigate prev on ArrowLeft when not in a widget group', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await left(); + click(item0); + left(); expect(document.activeElement).toBe(item0); }); - it('should navigate next in a widget group on ArrowRight', async () => { + it('should navigate next in a widget group on ArrowRight', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await right(); + click(item2); + right(); expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should navigate prev in a widget group on ArrowLeft', async () => { + it('should navigate prev in a widget group on ArrowLeft', () => { const item3 = getWidgetEl('item 3')!; - await click(item3); - await left(); + click(item3); + left(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should navigate last to first in a widget group on ArrowRight', async () => { + it('should navigate last to first in a widget group on ArrowRight', () => { const item4 = getWidgetEl('item 4')!; - await click(item4); - await right(); + click(item4); + right(); expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should navigate first to last in a widget group on ArrowLeft', async () => { + it('should navigate first to last in a widget group on ArrowLeft', () => { const item2 = getWidgetEl('item 2')!; - await click(item2); - await left(); + click(item2); + left(); expect(document.activeElement).toBe(getWidgetEl('item 4')); }); }); describe('with disabled toolbar', () => { - it('should not navigate on any key press', async () => { - await setupToolbar({disabled: true}); + it('should not navigate on any key press', () => { + setupToolbar({disabled: true}); const item0 = getWidgetEl('item 0')!; const initialActiveElement = document.activeElement; - await click(item0); + click(item0); expect(document.activeElement).toBe(initialActiveElement); - await right(); + right(); expect(document.activeElement).toBe(initialActiveElement); - await left(); + left(); expect(document.activeElement).toBe(initialActiveElement); - await down(); + down(); expect(document.activeElement).toBe(initialActiveElement); - await up(); + up(); expect(document.activeElement).toBe(initialActiveElement); - await home(); + home(); expect(document.activeElement).toBe(initialActiveElement); - await end(); + end(); expect(document.activeElement).toBe(initialActiveElement); }); }); describe('with wrapped toolbar widgets', () => { - beforeEach(async () => { + beforeEach(() => { TestBed.configureTestingModule({imports: [WrappedToolbarExample]}); fixture = TestBed.createComponent(WrappedToolbarExample) as any; - await fixture.whenStable(); + fixture.detectChanges(); }); - it('should navigate on click (wrapped)', async () => { + it('should navigate on click', () => { const widgets = fixture.debugElement .queryAll(By.css('[toolbar-button]')) .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); - await click(widgets[0]); + click(widgets[0]); expect(document.activeElement).toBe(widgets[0]); }); }); }); describe('Selection', () => { - beforeEach(async () => await setupToolbar()); + beforeEach(() => setupToolbar()); - it('should toggle the active item on Enter', async () => { + it('should toggle the active item on Enter', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await keydown('Enter'); + click(item0); + keydown('Enter'); expect(item0.getAttribute('aria-pressed')).toBe('false'); - await keydown('Enter'); + keydown('Enter'); expect(item0.getAttribute('aria-pressed')).toBe('true'); }); - it('should toggle the active item on Space', async () => { + it('should toggle the active item on Space', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); - await keydown(' '); + click(item0); + keydown(' '); expect(item0.getAttribute('aria-pressed')).toBe('false'); - await keydown(' '); + keydown(' '); expect(item0.getAttribute('aria-pressed')).toBe('true'); }); - it('should toggle the active item on click', async () => { + it('should toggle the active item on click', () => { const item0 = getWidgetEl('item 0')!; - await click(item0); + click(item0); expect(item0.getAttribute('aria-pressed')).toBe('true'); - await click(item0); + click(item0); expect(item0.getAttribute('aria-pressed')).toBe('false'); }); - it('should be able to select multiple items in the toolbar', async () => { + it('should be able to select multiple items in the toolbar', () => { const item0 = getWidgetEl('item 0')!; const item1 = getWidgetEl('item 1')!; - await click(item0); - await click(item1); + click(item0); + click(item1); expect(item0.getAttribute('aria-pressed')).toBe('true'); expect(item1.getAttribute('aria-pressed')).toBe('true'); }); - it('should not be able to select multiple items in a group', async () => { + it('should not be able to select multiple items in a group', () => { const item2 = getWidgetEl('item 2')!; const item3 = getWidgetEl('item 3')!; - await click(item2); - await click(item3); + click(item2); + click(item3); expect(item2.getAttribute('aria-pressed')).toBe('false'); expect(item3.getAttribute('aria-pressed')).toBe('true'); }); - it('should not select disabled items', async () => { + it('should not select disabled items', () => { fixture.componentInstance.widgets[1].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item1 = getWidgetEl('item 1')!; - await click(item1); + click(item1); expect(item1.getAttribute('aria-pressed')).toBe('false'); }); - it('should not select items in a disabled group', async () => { + it('should not select items in a disabled group', () => { fixture.componentInstance.groups[0].disabled.set(true); - await fixture.whenStable(); + fixture.detectChanges(); const item2 = getWidgetEl('item 2')!; - await click(item2); + click(item2); expect(item2.getAttribute('aria-pressed')).toBe('false'); }); }); - - describe('ARIA attributes and roles', () => { - beforeEach(async () => await setupToolbar()); - - it('should have role="toolbar"', () => { - expect(toolbarElement.getAttribute('role')).toBe('toolbar'); - }); - - it('should set aria-orientation based on input', async () => { - expect(toolbarElement.getAttribute('aria-orientation')).toBe('horizontal'); - fixture.componentInstance.orientation.set('vertical'); - await fixture.whenStable(); - expect(toolbarElement.getAttribute('aria-orientation')).toBe('vertical'); - }); - - it('should set aria-disabled based on input', async () => { - expect(toolbarElement.getAttribute('aria-disabled')).toBe('false'); - fixture.componentInstance.disabled.set(true); - await fixture.whenStable(); - expect(toolbarElement.getAttribute('aria-disabled')).toBe('true'); - }); - }); - - describe('Focus management', () => { - beforeEach(async () => await setupToolbar()); - - it('should have tabindex on widgets set by active state', async () => { - const widgets = getWidgetEls(); - expect(widgets[0].getAttribute('tabindex')).toBe('0'); - expect(widgets[1].getAttribute('tabindex')).toBe('-1'); - - await click(widgets[1]); - expect(widgets[0].getAttribute('tabindex')).toBe('-1'); - expect(widgets[1].getAttribute('tabindex')).toBe('0'); - }); - }); - - describe('Hard disabled state attributes', () => { - beforeEach(async () => await setupToolbar({softDisabled: false})); - - it('should set inert and disabled attributes on hard-disabled widgets', async () => { - fixture.componentInstance.widgets[0].disabled.set(true); - await fixture.whenStable(); - - const widgets = getWidgetEls(); - expect(widgets[0].hasAttribute('inert')).toBe(true); - expect(widgets[0].getAttribute('disabled')).toBe('true'); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupToolbar(); - }); - - it('should warn when duplicate values are detected inside ngToolbar', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ToolbarWithDuplicateValues], - }); - const duplicateFixture = TestBed.createComponent(ToolbarWithDuplicateValues); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith("Duplicate value 'item0' detected inside ngToolbar."); - }); - - it('should warn when ngToolbarWidgetGroup is outside ngToolbar', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [ToolbarGroupOutsideToolbar], - }); - const noToolbarFixture = TestBed.createComponent(ToolbarGroupOutsideToolbar); - noToolbarFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngToolbarWidgetGroup must be placed inside an ngToolbar container.', - ); - }); - }); }); @Component({ @@ -797,7 +673,6 @@ describe('Toolbar', () => {
`, imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarExample { orientation = signal<'vertical' | 'horizontal'>('horizontal'); @@ -838,44 +713,5 @@ export class SimpleToolbarButton {
`, imports: [Toolbar, SimpleToolbarButton], - changeDetection: ChangeDetectionStrategy.Eager, }) class WrappedToolbarExample {} - -@Component({ - template: ` -
- @for (item of items(); track item) { - - } -
- `, - imports: [Toolbar, ToolbarWidget], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ShuffledToolbarExample { - items = signal([{value: 'item 0'}, {value: 'item 1'}, {value: 'item 2'}]); -} - -@Component({ - template: ` -
- - -
- `, - imports: [Toolbar, ToolbarWidget], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ToolbarWithDuplicateValues {} - -@Component({ - template: ` -
- Widget Group Content -
- `, - imports: [ToolbarWidgetGroup], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class ToolbarGroupOutsideToolbar {} diff --git a/src/aria/toolbar/toolbar.ts b/src/aria/toolbar/toolbar.ts index 0d8ad21be0cd..f99ce45da71a 100644 --- a/src/aria/toolbar/toolbar.ts +++ b/src/aria/toolbar/toolbar.ts @@ -7,21 +7,20 @@ */ import { - afterNextRender, afterRenderEffect, - booleanAttribute, - computed, Directive, ElementRef, inject, + computed, input, - model, - OnDestroy, + booleanAttribute, signal, + model, } from '@angular/core'; -import {ToolbarPattern, ToolbarWidgetPattern, SortedCollection, reportViolations} from '../private'; +import {ToolbarPattern, ToolbarWidgetPattern} from '../private'; import {Directionality} from '@angular/cdk/bidi'; import type {ToolbarWidget} from './toolbar-widget'; +import {sortDirectives} from './utils'; /** * A toolbar widget container for a group of interactive widgets, such as @@ -41,6 +40,8 @@ import type {ToolbarWidget} from './toolbar-widget'; *
* ``` * + * @developerPreview 21.0 + * * @see [Toolbar](guide/aria/toolbar) */ @Directive({ @@ -54,25 +55,25 @@ import type {ToolbarWidget} from './toolbar-widget'; '(keydown)': '_pattern.onKeydown($event)', '(click)': '_pattern.onClick($event)', '(pointerdown)': '_pattern.onPointerdown($event)', - '(focusin)': '_pattern.onFocusIn()', + '(focusin)': '_onFocus()', }, }) -export class Toolbar implements OnDestroy { +export class Toolbar { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of widgets in the toolbar. */ - readonly _collection = new SortedCollection>(); + /** The TabList nested inside of the container. */ + private readonly _widgets = signal(new Set>()); /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; /** Sorted UIPatterns of the child widgets */ readonly _itemPatterns = computed[]>(() => - this._collection.orderedItems().map(widget => widget._pattern), + [...this._widgets()].sort(sortDirectives).map(widget => widget._pattern), ); /** Whether the toolbar is vertically or horizontally oriented. */ @@ -82,7 +83,7 @@ export class Toolbar implements OnDestroy { * Whether to allow disabled items to receive focus. When `true`, disabled items are * focusable but not interactive. When `false`, disabled items are skipped during navigation. */ - readonly softDisabled = input(true, {transform: booleanAttribute}); + softDisabled = input(true, {transform: booleanAttribute}); /** Whether the toolbar is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); @@ -91,7 +92,7 @@ export class Toolbar implements OnDestroy { readonly wrap = input(true, {transform: booleanAttribute}); /** The values of the selected widgets within the toolbar. */ - readonly value = model([]); + readonly values = model([]); /** The toolbar UIPattern. */ readonly _pattern: ToolbarPattern = new ToolbarPattern({ @@ -101,28 +102,37 @@ export class Toolbar implements OnDestroy { textDirection: this.textDirection, element: () => this._elementRef.nativeElement, getItem: e => this._getItem(e), - value: this.value, + values: this.values, }); - constructor() { - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); - - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); - }, - }); - } + /** Whether the toolbar has received focus yet. */ + private _hasBeenFocused = signal(false); - afterNextRender(() => { - this._collection.startObserving(this.element); + constructor() { + afterRenderEffect(() => { + if (!this._hasBeenFocused()) { + this._pattern.setDefaultState(); + } }); } - ngOnDestroy() { - this._collection.stopObserving(); + _onFocus() { + this._hasBeenFocused.set(true); + } + + _register(widget: ToolbarWidget) { + const widgets = this._widgets(); + if (!widgets.has(widget)) { + widgets.add(widget); + this._widgets.set(new Set(widgets)); + } + } + + _unregister(widget: ToolbarWidget) { + const widgets = this._widgets(); + if (widgets.delete(widget)) { + this._widgets.set(new Set(widgets)); + } } /** Finds the toolbar item associated with a given element. */ diff --git a/src/aria/toolbar/toolbar-tokens.ts b/src/aria/toolbar/utils.ts similarity index 68% rename from src/aria/toolbar/toolbar-tokens.ts rename to src/aria/toolbar/utils.ts index 3e3c083a8cb0..558f8508fde7 100644 --- a/src/aria/toolbar/toolbar-tokens.ts +++ b/src/aria/toolbar/utils.ts @@ -13,3 +13,13 @@ import type {ToolbarWidgetGroup} from './toolbar-widget-group'; export const TOOLBAR_WIDGET_GROUP = new InjectionToken>( 'TOOLBAR_WIDGET_GROUP', ); + +interface HasElement { + element: HTMLElement; +} + +export function sortDirectives(a: HasElement, b: HasElement) { + return (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_PRECEDING) > 0 + ? 1 + : -1; +} diff --git a/src/aria/tree/BUILD.bazel b/src/aria/tree/BUILD.bazel index a9d980cf40c9..c18b29d276f3 100644 --- a/src/aria/tree/BUILD.bazel +++ b/src/aria/tree/BUILD.bazel @@ -10,6 +10,7 @@ ng_project( ), deps = [ "//:node_modules/@angular/core", + "//src/aria/combobox", "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", @@ -27,7 +28,6 @@ ng_project( "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//src/aria/private/testing", "//src/cdk/testing/private", ], ) diff --git a/src/aria/tree/public-api.ts b/src/aria/tree/public-api.ts index 06449fda7530..a072a42a53d9 100644 --- a/src/aria/tree/public-api.ts +++ b/src/aria/tree/public-api.ts @@ -13,3 +13,10 @@ export {TreeItemGroup} from './tree-item-group'; // This needs to be re-exported, because it's used by the tree components. // See: https://github.com/angular/components/issues/30663. export {DeferredContent as ɵɵDeferredContent} from '../private'; +export { + Combobox as ɵɵCombobox, + ComboboxDialog as ɵɵComboboxDialog, + ComboboxInput as ɵɵComboboxInput, + ComboboxPopup as ɵɵComboboxPopup, + ComboboxPopupContainer as ɵɵComboboxPopupContainer, +} from '../combobox'; diff --git a/src/aria/tree/testing/BUILD.bazel b/src/aria/tree/testing/BUILD.bazel deleted file mode 100644 index 63e72fd18bdf..000000000000 --- a/src/aria/tree/testing/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "testing", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//src/cdk/testing", - ], -) - -filegroup( - name = "source-files", - srcs = glob(["**/*.ts"]), -) - -ng_project( - name = "unit_tests_lib", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":testing", - "//:node_modules/@angular/common", - "//:node_modules/@angular/core", - "//src/aria/tree", - "//src/cdk/testing", - "//src/cdk/testing/testbed", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_tests_lib"], -) diff --git a/src/aria/tree/testing/index.ts b/src/aria/tree/testing/index.ts deleted file mode 100644 index 52b3c7a5156f..000000000000 --- a/src/aria/tree/testing/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './public-api'; diff --git a/src/aria/tree/testing/item-harness.ts b/src/aria/tree/testing/item-harness.ts deleted file mode 100644 index e4e98dfbfc04..000000000000 --- a/src/aria/tree/testing/item-harness.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ContentContainerComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; -import {TreeItemHarnessFilters} from './tree-harness-filters'; - -/** Harness for interacting with an Aria tree item. */ -export class TreeItemHarness extends ContentContainerComponentHarness { - static hostSelector = '[ngTreeItem]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a tree item with specific attributes. - * @param options Options for narrowing the search - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: TreeItemHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(TreeItemHarness, options) - .addOption('text', options.text, (harness, text) => - HarnessPredicate.stringMatches(harness.getText(), text), - ) - .addOption( - 'disabled', - options.disabled, - async (harness, disabled) => (await harness.isDisabled()) === disabled, - ) - .addOption( - 'expanded', - options.expanded, - async (harness, expanded) => (await harness.isExpanded()) === expanded, - ) - .addOption( - 'selected', - options.selected, - async (harness, selected) => (await harness.isSelected()) === selected, - ) - .addOption( - 'level', - options.level, - async (harness, level) => (await harness.getLevel()) === level, - ); - } - - /** Whether the tree item is expanded. */ - async isExpanded(): Promise { - return (await this._getHostAttribute('aria-expanded')) === 'true'; - } - - /** Whether the tree item is disabled. */ - async isDisabled(): Promise { - return (await this._getHostAttribute('aria-disabled')) === 'true'; - } - - /** Whether the tree item is selected. */ - async isSelected(): Promise { - return (await this._getHostAttribute('aria-selected')) === 'true'; - } - - /** Gets the level of the tree item. Note that this gets the aria-level and is 1 indexed. */ - async getLevel(): Promise { - const level = (await this._getHostAttribute('aria-level')) ?? '1'; - return parseInt(level); - } - - /** Gets the tree item's text. */ - async getText(): Promise { - return (await this.host()).text({exclude: '[ngTreeItem], [ngTreeItemGroup]'}); - } - - /** Clicks the tree item. */ - async click(): Promise { - return (await this.host()).click(); - } - - /** Focuses the tree item. */ - async focus(): Promise { - return (await this.host()).focus(); - } - - /** Blurs the tree item. */ - async blur(): Promise { - return (await this.host()).blur(); - } - - /** Whether the tree item is active. */ - async isActive(): Promise { - return (await this._getHostAttribute('data-active')) === 'true'; - } - - /** Whether the tree item has focus. */ - async isFocused(): Promise { - return (await this.host()).isFocused(); - } - - private async _getHostAttribute(attributeName: string): Promise { - return (await this.host()).getAttribute(attributeName); - } -} diff --git a/src/aria/tree/testing/public-api.ts b/src/aria/tree/testing/public-api.ts deleted file mode 100644 index 8795f62017b1..000000000000 --- a/src/aria/tree/testing/public-api.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './item-harness'; -export * from './tree-harness'; -export * from './tree-harness-filters'; diff --git a/src/aria/tree/testing/tree-harness-filters.ts b/src/aria/tree/testing/tree-harness-filters.ts deleted file mode 100644 index a0177eaa3bf7..000000000000 --- a/src/aria/tree/testing/tree-harness-filters.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {BaseHarnessFilters} from '@angular/cdk/testing'; - -/** A set of criteria that can be used to filter a list of tree harness instances */ -export interface TreeHarnessFilters extends BaseHarnessFilters {} - -/** A set of criteria that can be used to filter a list of tree item harness instances. */ -export interface TreeItemHarnessFilters extends BaseHarnessFilters { - /** Only find instances whose text matches the given value. */ - text?: string | RegExp; - - /** Only find instances whose disabled state matches the given value. */ - disabled?: boolean; - - /** Only find instances whose expansion state matches the given value. */ - expanded?: boolean; - - /** Only find instances whose selection state matches the given value. */ - selected?: boolean; - - /** Only find instances whose level matches the given value. */ - level?: number; -} diff --git a/src/aria/tree/testing/tree-harness.spec.ts b/src/aria/tree/testing/tree-harness.spec.ts deleted file mode 100644 index 7c7800a02006..000000000000 --- a/src/aria/tree/testing/tree-harness.spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -import {Component} from '@angular/core'; -import {NgTemplateOutlet} from '@angular/common'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Tree, TreeItem, TreeItemGroup} from '../../tree'; -import {TreeHarness} from './tree-harness'; - -describe('TreeHarness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - fixture = TestBed.createComponent(TreeHarnessTest); - fixture.detectChanges(); - loader = TestbedHarnessEnvironment.loader(fixture); - }); - - it('should load harness with 2 tress', async () => { - const trees = await loader.getAllHarnesses(TreeHarness); - expect(trees.length).toBe(1); - }); - - it('should get correct number of children and descendants', async () => { - const tree = await loader.getHarness(TreeHarness); - const items = await tree.getItems(); - expect(items.length).toBe(5); - - await items[0].click(); - expect((await tree.getItems()).length).toBe(8); - }); - - it('should correctly get correct item with text', async () => { - const tree = await loader.getHarness(TreeHarness); - const items = await tree.getItems({text: /\.json$/}); - expect(items.length).toBe(2); - const item = items[0]; - - expect(await item.getText()).toBe('angular.json'); - expect(await item.getLevel()).toBe(1); - expect(await item.isDisabled()).toBe(false); - expect(await item.isExpanded()).toBe(false); - }); - - it('should toggle expansion', async () => { - const tree = await loader.getHarness(TreeHarness); - const item = (await tree.getItems())[0]; - - expect(await item.isExpanded()).toBe(false); - await item.click(); - expect(await item.isExpanded()).toBe(true); - await item.click(); - expect(await item.isExpanded()).toBe(false); - }); - - it('should correctly get tree structure', async () => { - const tree = await loader.getHarness(TreeHarness); - - expect(await tree.getTreeStructure()).toEqual({ - children: [ - {text: 'public'}, - {text: 'src'}, - {text: 'angular.json'}, - {text: 'package.json'}, - {text: 'README.md'}, - ], - }); - - const firstGroup = (await tree.getItems({text: 'public'}))[0]; - await firstGroup.click(); - - expect(await tree.getTreeStructure()).toEqual({ - children: [ - { - text: 'public', - children: [{text: 'index.html'}, {text: 'favicon.ico'}, {text: 'styles.css'}], - }, - {text: 'src'}, - {text: 'angular.json'}, - {text: 'package.json'}, - {text: 'README.md'}, - ], - }); - - const secondGroup = (await tree.getItems({text: 'src'}))[0]; - await secondGroup.click(); - - expect(await tree.getTreeStructure()).toEqual({ - children: [ - { - text: 'public', - children: [{text: 'index.html'}, {text: 'favicon.ico'}, {text: 'styles.css'}], - }, - { - text: 'src', - children: [ - {text: 'app'}, - {text: 'assets'}, - {text: 'environments'}, - {text: 'main.ts'}, - {text: 'polyfills.ts'}, - {text: 'styles.css'}, - {text: 'test.ts'}, - ], - }, - {text: 'angular.json'}, - {text: 'package.json'}, - {text: 'README.md'}, - ], - }); - }); - - it('should be able to get whether a tree item has focus', async () => { - const tree = await loader.getHarness(TreeHarness); - const item = (await tree.getItems({text: 'angular.json'}))[0]; - - expect(await item.isFocused()).toBeFalse(); - - await item.focus(); - expect(await item.isFocused()).toBeTrue(); - - await item.blur(); - expect(await item.isFocused()).toBeFalse(); - }); - - it('should be able to get whether a tree item is active', async () => { - const tree = await loader.getHarness(TreeHarness); - const item = (await tree.getItems({text: 'public'}))[0]; - - expect(await item.isActive()).toBeTrue(); - }); -}); - -interface TreeNode { - name: string; - value: string; - children?: TreeNode[]; - disabled?: boolean; - expanded?: boolean; -} - -@Component({ - template: ` -
    - -
- - - @for (node of nodes; track node.value) { -
  • - {{ node.name }} -
  • - - @if (node.children) { -
      - - - -
    - } - } -
    - `, - imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet], -}) -class TreeHarnessTest { - readonly nodes: TreeNode[] = [ - { - name: 'public', - value: 'public', - children: [ - {name: 'index.html', value: 'public/index.html'}, - {name: 'favicon.ico', value: 'public/favicon.ico'}, - {name: 'styles.css', value: 'public/styles.css'}, - ], - expanded: false, - }, - { - name: 'src', - value: 'src', - children: [ - { - name: 'app', - value: 'src/app', - children: [ - {name: 'app.component.ts', value: 'src/app/app.component.ts'}, - {name: 'app.module.ts', value: 'src/app/app.module.ts', disabled: true}, - {name: 'app.css', value: 'src/app/app.css'}, - ], - expanded: false, - }, - { - name: 'assets', - value: 'src/assets', - children: [{name: 'logo.png', value: 'src/assets/logo.png'}], - expanded: false, - }, - { - name: 'environments', - value: 'src/environments', - children: [ - { - name: 'environment.prod.ts', - value: 'src/environments/environment.prod.ts', - expanded: false, - }, - {name: 'environment.ts', value: 'src/environments/environment.ts'}, - ], - expanded: false, - }, - {name: 'main.ts', value: 'src/main.ts'}, - {name: 'polyfills.ts', value: 'src/polyfills.ts'}, - {name: 'styles.css', value: 'src/styles.css', disabled: true}, - {name: 'test.ts', value: 'src/test.ts'}, - ], - expanded: false, - }, - {name: 'angular.json', value: 'angular.json'}, - {name: 'package.json', value: 'package.json'}, - {name: 'README.md', value: 'README.md'}, - ]; -} diff --git a/src/aria/tree/testing/tree-harness.ts b/src/aria/tree/testing/tree-harness.ts deleted file mode 100644 index b97c65e18637..000000000000 --- a/src/aria/tree/testing/tree-harness.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ComponentHarness, HarnessPredicate, parallel} from '@angular/cdk/testing'; -import {TreeItemHarness} from './item-harness'; -import {TreeHarnessFilters, TreeItemHarnessFilters} from './tree-harness-filters'; - -export interface TextTree { - text?: string; - children?: TextTree[]; -} - -/** Harness for interacting with an Aria tree in tests. */ -export class TreeHarness extends ComponentHarness { - static hostSelector = '[ngTree]'; - - /** - * Gets a `HarnessPredicate` that can be used to search for a tree with specific attributes. - * @param options Options for narrowing the search - * @return a `HarnessPredicate` configured with the given options. - */ - static with(options: TreeHarnessFilters = {}): HarnessPredicate { - return new HarnessPredicate(TreeHarness, options); - } - - /** Gets all of the items in the tree. */ - async getItems(filter: TreeItemHarnessFilters = {}): Promise { - return this.locatorForAll(TreeItemHarness.with(filter))(); - } - - /** - * Gets an object representation for the visible tree structure - * If an item is under an unexpanded item it will not be included. - */ - async getTreeStructure(): Promise { - const items = await this.getItems(); - const itemInformation = await parallel(() => - items.map(item => parallel(() => [item.getLevel(), item.getText(), item.isExpanded()])), - ); - return this._getTreeStructure(itemInformation, 1, true); - } - - /** - * Recursively collect the structured text of the tree items. - * @param items A list of tree items - * @param level The level of items that are being accounted for during this iteration - * @param parentExpanded Whether the parent of the first item in param items is expanded - */ - private _getTreeStructure( - items: [number, string, boolean][], - level: number, - parentExpanded: boolean, - ): TextTree { - const result: TextTree = {}; - for (let i = 0; i < items.length; i++) { - const [itemLevel, text, expanded] = items[i]; - const nextItemLevel = items[i + 1]?.[0] ?? -1; - - // Return the accumulated value for the current level once we reach a shallower level item - if (itemLevel < level) { - return result; - } - // Skip deeper level items during this iteration, they will be picked up in a later iteration - if (itemLevel > level) { - continue; - } - // Only add to representation if it is visible (parent is expanded) - if (parentExpanded) { - // Collect the data under this item according to the following rules: - // 1. If the next item in the list is a sibling of the current item add it to the child list - // 2. If the next item is a child of the current item, get the sub-tree structure for the - // child and add it under this item - // 3. If the next item has a shallower level, we've reached the end of the child items for - // the current parent. - if (nextItemLevel === level) { - this._addChildToItem(result, {text}); - } else if (nextItemLevel > level) { - let children = this._getTreeStructure( - items.slice(i + 1), - nextItemLevel, - expanded, - )?.children; - let child = children ? {text, children} : {text}; - this._addChildToItem(result, child); - } else { - this._addChildToItem(result, {text}); - return result; - } - } - } - return result; - } - - private _addChildToItem(result: TextTree, child: TextTree) { - result.children ? result.children.push(child) : (result.children = [child]); - } -} diff --git a/src/aria/tree/tree-item-group.ts b/src/aria/tree/tree-item-group.ts index 94d3a8e19358..01be388d4de1 100644 --- a/src/aria/tree/tree-item-group.ts +++ b/src/aria/tree/tree-item-group.ts @@ -16,8 +16,9 @@ import { OnInit, OnDestroy, } from '@angular/core'; -import {TreeItemPattern, DeferredContent, sortDirectives} from '../private'; +import {TreeItemPattern, DeferredContent} from '../private'; import type {TreeItem} from './tree-item'; +import {sortDirectives} from './utils'; /** * Group that contains children tree items. @@ -37,6 +38,8 @@ import type {TreeItem} from './tree-item'; * * ``` * + * @developerPreview 21.0 + * * @see [Tree](guide/aria/tree) */ @Directive({ diff --git a/src/aria/tree/tree-item.ts b/src/aria/tree/tree-item.ts index cc799708bb9d..728ec42701d5 100644 --- a/src/aria/tree/tree-item.ts +++ b/src/aria/tree/tree-item.ts @@ -19,11 +19,13 @@ import { Signal, OnInit, OnDestroy, + afterNextRender, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; -import {TreeItemPattern, DeferredContentAware, HasElement} from '../private'; +import {ComboboxTreePattern, TreeItemPattern, DeferredContentAware} from '../private'; import {Tree} from './tree'; import {TreeItemGroup} from './tree-item-group'; +import {HasElement} from './utils'; /** * A selectable and expandable item in an `ngTree`. @@ -37,6 +39,8 @@ import {TreeItemGroup} from './tree-item-group'; * Item Label * * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTreeItem]', @@ -119,18 +123,22 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr constructor() { super(); - afterRenderEffect({ - write: () => { - this.contentVisible.set(this._pattern.expanded()); - }, + afterNextRender(() => { + if (this.tree()._pattern instanceof ComboboxTreePattern) { + this.preserveContent.set(true); + } + }); + // Connect the group's hidden state to the DeferredContentAware's visibility. + afterRenderEffect(() => { + this.tree()._pattern instanceof ComboboxTreePattern + ? this.contentVisible.set(true) + : this.contentVisible.set(this._pattern.expanded()); }); } ngOnInit() { - if (this.parent() instanceof TreeItemGroup) { - (this.parent() as TreeItemGroup)._register(this); - } - this.tree()._collection.register(this); + this.parent()._register(this); + this.tree()._register(this); const treePattern = computed(() => this.tree()._pattern); const parentPattern = computed(() => { @@ -151,10 +159,8 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr } ngOnDestroy() { - if (this.parent() instanceof TreeItemGroup) { - (this.parent() as TreeItemGroup)._unregister(this); - } - this.tree()._collection.unregister(this); + this.parent()._unregister(this); + this.tree()._unregister(this); } _register(group: TreeItemGroup) { diff --git a/src/aria/tree/tree.spec.ts b/src/aria/tree/tree.spec.ts index 28f2c17a6795..8964b432fc90 100644 --- a/src/aria/tree/tree.spec.ts +++ b/src/aria/tree/tree.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {NgTemplateOutlet} from '@angular/common'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -7,7 +7,6 @@ import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/te import {Tree} from './tree'; import {TreeItem} from './tree-item'; import {TreeItemGroup} from './tree-item-group'; -import {waitForMicrotasks} from '../private/testing/test-helpers'; interface ModifierKeys { ctrlKey?: boolean; @@ -23,42 +22,38 @@ describe('Tree', () => { let treeInstance: Tree; let treeItemElements: HTMLElement[]; - const keydown = async (key: string, modifierKeys: ModifierKeys = {}) => { + const keydown = (key: string, modifierKeys: ModifierKeys = {}) => { const event = new KeyboardEvent('keydown', {key, bubbles: true, ...modifierKeys}); treeElement.dispatchEvent(event); - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); }; - const up = async (modifierKeys?: ModifierKeys) => await keydown('ArrowUp', modifierKeys); - const down = async (modifierKeys?: ModifierKeys) => await keydown('ArrowDown', modifierKeys); - const left = async (modifierKeys?: ModifierKeys) => await keydown('ArrowLeft', modifierKeys); - const right = async (modifierKeys?: ModifierKeys) => await keydown('ArrowRight', modifierKeys); - const home = async (modifierKeys?: ModifierKeys) => await keydown('Home', modifierKeys); - const end = async (modifierKeys?: ModifierKeys) => await keydown('End', modifierKeys); - const enter = async (modifierKeys?: ModifierKeys) => await keydown('Enter', modifierKeys); - const space = async (modifierKeys?: ModifierKeys) => await keydown(' ', modifierKeys); - const shift = async () => await keydown('Shift'); - const type = async (chars: string) => { + const pointerDown = (target: HTMLElement, eventInit: PointerEventInit = {}) => { + target.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, ...eventInit})); + fixture.detectChanges(); + defineTestVariables(); + }; + + const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys); + const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys); + const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys); + const right = (modifierKeys?: ModifierKeys) => keydown('ArrowRight', modifierKeys); + const home = (modifierKeys?: ModifierKeys) => keydown('Home', modifierKeys); + const end = (modifierKeys?: ModifierKeys) => keydown('End', modifierKeys); + const enter = (modifierKeys?: ModifierKeys) => keydown('Enter', modifierKeys); + const space = (modifierKeys?: ModifierKeys) => keydown(' ', modifierKeys); + const shift = () => keydown('Shift'); + const type = (chars: string) => { for (const char of chars) { - await keydown(char); + keydown(char); } }; - const clickHelper = async (target: HTMLElement, eventInit: PointerEventInit = {}) => { - target.dispatchEvent( - new PointerEvent('click', { - bubbles: true, - ...eventInit, - }), - ); - await fixture.whenStable(); - defineTestVariables(); - }; - const click = async (target: HTMLElement) => await clickHelper(target); - const shiftClick = async (target: HTMLElement) => await clickHelper(target, {shiftKey: true}); - const ctrlClick = async (target: HTMLElement) => await clickHelper(target, {ctrlKey: true}); + const click = (target: HTMLElement) => pointerDown(target); + const shiftClick = (target: HTMLElement) => pointerDown(target, {shiftKey: true}); + const ctrlClick = (target: HTMLElement) => pointerDown(target, {ctrlKey: true}); - async function setupTestTree(textDirection: Direction = 'ltr') { + function setupTestTree(textDirection: Direction = 'ltr') { TestBed.configureTestingModule({ providers: [provideFakeDirectionality(textDirection)], }); @@ -66,7 +61,7 @@ describe('Tree', () => { fixture = TestBed.createComponent(TestTreeComponent); testComponent = fixture.componentInstance; - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } @@ -79,10 +74,10 @@ describe('Tree', () => { treeItemElements = treeItemDebugElements.map(debugEl => debugEl.nativeElement); } - async function updateTree( + function updateTree( config: { nodes?: TestTreeNode[]; - value?: string[]; + values?: string[]; disabled?: boolean; orientation?: 'horizontal' | 'vertical'; multi?: boolean; @@ -92,11 +87,10 @@ describe('Tree', () => { selectionMode?: 'follow' | 'explicit'; nav?: boolean; currentType?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'; - tabIndex?: number; } = {}, ) { if (config.nodes !== undefined) testComponent.nodes.set(config.nodes); - if (config.value !== undefined) treeInstance.value.set(config.value); + if (config.values !== undefined) treeInstance.values.set(config.values); if (config.disabled !== undefined) testComponent.disabled.set(config.disabled); if (config.orientation !== undefined) testComponent.orientation.set(config.orientation); if (config.multi !== undefined) testComponent.multi.set(config.multi); @@ -106,13 +100,12 @@ describe('Tree', () => { if (config.selectionMode !== undefined) testComponent.selectionMode.set(config.selectionMode); if (config.nav !== undefined) testComponent.nav.set(config.nav); if (config.currentType !== undefined) testComponent.currentType.set(config.currentType); - if (config.tabIndex !== undefined) testComponent.tabIndex.set(config.tabIndex); - await fixture.whenStable(); + fixture.detectChanges(); defineTestVariables(); } - async function updateTreeItemByValue(value: string, config: Partial>) { + function updateTreeItemByValue(value: string, config: Partial>) { const newNodes = JSON.parse(JSON.stringify(testComponent.nodes())); const childrenList = [newNodes]; while (childrenList.length > 0) { @@ -124,7 +117,7 @@ describe('Tree', () => { if (config.children !== undefined) node.children = config.children; if (config.disabled !== undefined) node.disabled = config.disabled; if (config.selectable !== undefined) node.selectable = config.selectable; - await updateTree({nodes: newNodes}); + updateTree({nodes: newNodes}); return; } if (node.children) { @@ -151,97 +144,33 @@ describe('Tree', () => { return item?.getAttribute('data-value') ?? undefined; } - async function expandAll() { + function expandAll() { const fruitsEl = getTreeItemElementByValue('fruits')!; - await click(fruitsEl); + click(fruitsEl); const berriesEl = getTreeItemElementByValue('berries')!; - await click(berriesEl); + click(berriesEl); const vegetablesEl = getTreeItemElementByValue('vegetables')!; - await click(vegetablesEl); - await updateTree({value: []}); + click(vegetablesEl); + updateTree({values: []}); } afterEach(async () => { - await fixture.whenStable(); + fixture.detectChanges(); await runAccessibilityChecks(fixture.nativeElement); }); - describe('dynamic updates', () => { - it('should update item order correctly after items are shuffled', async () => { - await setupTestTree(); - await expandAll(); - await fixture.whenStable(); - - const treeDirective = fixture.debugElement.query(By.directive(Tree)).injector.get(Tree); - const itemsBefore = treeDirective._pattern.inputs.items(); - expect(itemsBefore.length).toBe(11); - expect(itemsBefore[0].value()).toBe('fruits'); - - // Shuffle top-level nodes: move fruits to end - const nodes = testComponent.nodes(); - const firstNode = nodes.shift()!; - nodes.push(firstNode); - testComponent.nodes.set([...nodes]); - await fixture.whenStable(); - await waitForMicrotasks(); - - const itemsAfter = treeDirective._pattern.inputs.items(); - expect(itemsAfter.length).toBe(11); - expect(itemsAfter[0].value()).toBe('vegetables'); - }); - }); - - describe('structural validations', () => { - let consoleSpy: jasmine.Spy; - - beforeEach(() => { - consoleSpy = spyOn(console, 'warn'); - }); - - afterEach(async () => { - TestBed.resetTestingModule(); - await setupTestTree(); - }); - - it('should warn when duplicate values are detected inside ngTree', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [TreeWithDuplicateValues], - }); - const duplicateFixture = TestBed.createComponent(TreeWithDuplicateValues); - duplicateFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - "Duplicate tree item value 'item0' detected inside ngTree.", - ); - }); - - it('should warn when single-select tree has multiple selected values', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [SingleSelectTreeWithMultipleValues], - }); - const singleSelectFixture = TestBed.createComponent(SingleSelectTreeWithMultipleValues); - singleSelectFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'A single-select tree should not have multiple selected options. Selected options: item0, item1', - ); - }); - }); - describe('ARIA attributes and roles', () => { describe('default configuration', () => { - beforeEach(async () => { - await setupTestTree(); + beforeEach(() => { + setupTestTree(); }); it('should correctly set the role attribute to "tree" for Tree', () => { expect(treeElement.getAttribute('role')).toBe('tree'); }); - it('should correctly set the role attribute to "treeitem" for TreeItem', async () => { - await expandAll(); + it('should correctly set the role attribute to "treeitem" for TreeItem', () => { + expandAll(); expect(getTreeItemElementByValue('fruits')!.getAttribute('role')).toBe('treeitem'); expect(getTreeItemElementByValue('vegetables')!.getAttribute('role')).toBe('treeitem'); @@ -280,8 +209,8 @@ describe('Tree', () => { expect(fruitsItem.getAttribute('aria-expanded')).toBe('false'); }); - it('should set aria-level, aria-setsize, and aria-posinset correctly', async () => { - await expandAll(); + it('should set aria-level, aria-setsize, and aria-posinset correctly', () => { + expandAll(); const fruits = getTreeItemElementByValue('fruits')!; expect(fruits.getAttribute('aria-level')).toBe('1'); @@ -324,43 +253,38 @@ describe('Tree', () => { }); describe('custom configuration', () => { - beforeEach(async () => { - await setupTestTree(); + beforeEach(() => { + setupTestTree(); }); - it('should set aria-orientation to "horizontal"', async () => { - await updateTree({orientation: 'horizontal'}); + it('should set aria-orientation to "horizontal"', () => { + updateTree({orientation: 'horizontal'}); expect(treeElement.getAttribute('aria-orientation')).toBe('horizontal'); }); - it('should set aria-multiselectable to "true"', async () => { - await updateTree({multi: true}); + it('should set aria-multiselectable to "true"', () => { + updateTree({multi: true}); expect(treeElement.getAttribute('aria-multiselectable')).toBe('true'); }); - it('should set aria-disabled to "true" for the tree', async () => { - await updateTree({disabled: true}); + it('should set aria-disabled to "true" for the tree', () => { + updateTree({disabled: true}); expect(treeElement.getAttribute('aria-disabled')).toBe('true'); }); - it('should be able to override tabindex', async () => { - await updateTree({tabIndex: -1}); - expect(treeElement.getAttribute('tabindex')).toBe('-1'); - }); - - it('should set aria-disabled to "true" for disabled items', async () => { - await updateTreeItemByValue('fruits', {disabled: true}); + it('should set aria-disabled to "true" for disabled items', () => { + updateTreeItemByValue('fruits', {disabled: true}); const fruitsItem = getTreeItemElementByValue('fruits')!; expect(fruitsItem.getAttribute('aria-disabled')).toBe('true'); }); - it('should set aria-selected to "true" for selected items', async () => { - await expandAll(); - await updateTree({value: ['apple']}); + it('should set aria-selected to "true" for selected items', () => { + expandAll(); + updateTree({values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-selected')).toBe('true'); @@ -368,76 +292,76 @@ describe('Tree', () => { expect(fruitsItem.getAttribute('aria-selected')).toBe('false'); }); - it('should set aria-expanded to "true" for expanded items', async () => { - await right(); + it('should set aria-expanded to "true" for expanded items', () => { + right(); const fruitsItem = getTreeItemElementByValue('fruits')!; expect(fruitsItem.getAttribute('aria-expanded')).toBe('true'); }); - it('should set aria-current to specific current type when nav="true"', async () => { - await expandAll(); - await updateTree({nav: true, value: ['apple']}); + it('should set aria-current to specific current type when nav="true"', () => { + expandAll(); + updateTree({nav: true, values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; const bananaItem = getTreeItemElementByValue('banana')!; expect(appleItem.getAttribute('aria-current')).toBe('page'); expect(bananaItem.hasAttribute('aria-current')).toBe(false); - await updateTree({currentType: 'location'}); + updateTree({currentType: 'location'}); expect(appleItem.getAttribute('aria-current')).toBe('location'); }); - it('should not set aria-current when not selectable', async () => { - await expandAll(); - await updateTree({nav: true, value: ['apple']}); + it('should not set aria-current when not selectable', () => { + expandAll(); + updateTree({nav: true, values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-current')).toBe('page'); - await updateTreeItemByValue('apple', {selectable: false}); + updateTreeItemByValue('apple', {selectable: false}); expect(appleItem.hasAttribute('aria-current')).toBe(false); }); - it('should not set aria-selected when nav="true"', async () => { - await expandAll(); + it('should not set aria-selected when nav="true"', () => { + expandAll(); - await updateTree({value: ['apple'], nav: true}); + updateTree({values: ['apple'], nav: true}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.hasAttribute('aria-selected')).toBe(false); - await updateTree({nav: false}); + updateTree({nav: false}); expect(appleItem.getAttribute('aria-selected')).toBe('true'); }); - it('should not set aria-selected when not selectable', async () => { - await expandAll(); - await updateTree({value: ['apple']}); + it('should not set aria-selected when not selectable', () => { + expandAll(); + updateTree({values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-selected')).toBe('true'); - await updateTreeItemByValue('apple', {selectable: false}); + updateTreeItemByValue('apple', {selectable: false}); expect(appleItem.hasAttribute('aria-selected')).toBe(false); }); }); describe('roving focus mode (focusMode="roving")', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({focusMode: 'roving'}); + beforeEach(() => { + setupTestTree(); + updateTree({focusMode: 'roving'}); }); it('should set tabindex="-1" for the tree', () => { expect(treeElement.getAttribute('tabindex')).toBe('-1'); }); - it('should set tabindex="0" for the tree when disabled when softDisabled is false', async () => { - await updateTree({disabled: true, focusMode: 'roving', softDisabled: false}); + it('should set tabindex="0" for the tree when disabled when softDisabled is false', () => { + updateTree({disabled: true, focusMode: 'roving', softDisabled: false}); expect(treeElement.getAttribute('tabindex')).toBe('0'); }); - it('should set tabindex="0" for the tree when disabled when softDisabled is true', async () => { - await updateTree({disabled: true, focusMode: 'roving'}); + it('should set tabindex="0" for the tree when disabled when softDisabled is true', () => { + updateTree({disabled: true, focusMode: 'roving'}); expect(treeElement.getAttribute('tabindex')).toBe('0'); }); @@ -454,8 +378,8 @@ describe('Tree', () => { expect(dairyItem.getAttribute('tabindex')).toBe('-1'); }); - it('should set initial focus (tabindex="0") on the first selected item', async () => { - await updateTree({value: ['vegetables', 'dairy'], focusMode: 'roving'}); + it('should set initial focus (tabindex="0") on the first selected item', () => { + updateTree({values: ['vegetables', 'dairy'], focusMode: 'roving'}); const fruitsItem = getTreeItemElementByValue('fruits')!; const vegetablesItem = getTreeItemElementByValue('vegetables')!; @@ -474,9 +398,9 @@ describe('Tree', () => { }); describe('activedescendant focus mode (focusMode="activedescendant")', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({focusMode: 'activedescendant'}); + beforeEach(() => { + setupTestTree(); + updateTree({focusMode: 'activedescendant'}); }); it('should set tabindex="0" for the tree', () => { @@ -488,15 +412,15 @@ describe('Tree', () => { expect(treeElement.getAttribute('aria-activedescendant')).toBe(fruitsItem.id); }); - it('should set aria-activedescendant to the ID of the first selected item', async () => { - await updateTree({value: ['vegetables', 'dairy'], focusMode: 'activedescendant'}); + it('should set aria-activedescendant to the ID of the first selected item', () => { + updateTree({values: ['vegetables', 'dairy'], focusMode: 'activedescendant'}); const vegetablesItem = getTreeItemElementByValue('vegetables')!; expect(treeElement.getAttribute('aria-activedescendant')).toBe(vegetablesItem.id); }); - it('should set tabindex="-1" for all items', async () => { - await expandAll(); + it('should set tabindex="-1" for all items', () => { + expandAll(); expect(getTreeItemElementByValue('fruits')!.getAttribute('tabindex')).toBe('-1'); expect(getTreeItemElementByValue('apple')!.getAttribute('tabindex')).toBe('-1'); @@ -514,10 +438,10 @@ describe('Tree', () => { }); describe('value and selection', () => { - it('should select items based on the initial value input', async () => { - await setupTestTree(); - await expandAll(); - await updateTree({value: ['apple', 'strawberry', 'carrot']}); + it('should select items based on the initial value input', () => { + setupTestTree(); + expandAll(); + updateTree({values: ['apple', 'strawberry', 'carrot']}); expect(getTreeItemElementByValue('apple')!.getAttribute('aria-selected')).toBe('true'); expect(getTreeItemElementByValue('strawberry')!.getAttribute('aria-selected')).toBe('true'); @@ -527,55 +451,55 @@ describe('Tree', () => { describe('pointer interactions', () => { describe('single select (multi=false, selectionMode="explicit")', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({multi: false, selectionMode: 'explicit'}); + beforeEach(() => { + setupTestTree(); + updateTree({multi: false, selectionMode: 'explicit'}); }); - it('should select an item on click and deselect others', async () => { - await right(); + it('should select an item on click and deselect others', () => { + right(); const appleEl = getTreeItemElementByValue('apple')!; const bananaEl = getTreeItemElementByValue('banana')!; - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); expect(appleEl.getAttribute('aria-selected')).toBe('true'); expect(bananaEl.getAttribute('aria-selected')).toBe('false'); - await click(bananaEl); - expect(treeInstance.value()).toEqual(['banana']); + click(bananaEl); + expect(treeInstance.values()).toEqual(['banana']); expect(appleEl.getAttribute('aria-selected')).toBe('false'); expect(bananaEl.getAttribute('aria-selected')).toBe('true'); }); }); describe('multi select (multi=true)', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({multi: true}); + beforeEach(() => { + setupTestTree(); + updateTree({multi: true}); // Expands vegetables and fruits - await down(); - await right(); - await up(); - await right(); + down(); + right(); + up(); + right(); }); describe('selectionMode="explicit"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'explicit'}); + beforeEach(() => { + updateTree({selectionMode: 'explicit'}); }); - it('should select a range with shift+click', async () => { + it('should select a range with shift+click', () => { const appleEl = getTreeItemElementByValue('apple')!; const carrotEl = getTreeItemElementByValue('carrot')!; - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); expect(appleEl.getAttribute('aria-selected')).toBe('true'); shiftClick(carrotEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'apple', 'banana', 'berries', @@ -584,79 +508,79 @@ describe('Tree', () => { ]); }); - it('should toggle selection of an item on simple click, leaving other selections intact', async () => { + it('should toggle selection of an item on simple click, leaving other selections intact', () => { const appleEl = getTreeItemElementByValue('apple')!; const bananaEl = getTreeItemElementByValue('banana')!; - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); - await click(bananaEl); - expect(treeInstance.value()).toEqual(['apple', 'banana']); + click(bananaEl); + expect(treeInstance.values()).toEqual(['apple', 'banana']); - await click(appleEl); - expect(treeInstance.value()).toEqual(['banana']); + click(appleEl); + expect(treeInstance.values()).toEqual(['banana']); }); describe('selectable=false', () => { - it('should not select an item on click', async () => { - await updateTree({value: ['banana']}); - await updateTreeItemByValue('apple', {selectable: false}); + it('should not select an item on click', () => { + updateTree({values: ['banana']}); + updateTreeItemByValue('apple', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; - await click(appleEl); - expect(treeInstance.value()).not.toContain('apple'); - expect(treeInstance.value()).toContain('banana'); + click(appleEl); + expect(treeInstance.values()).not.toContain('apple'); + expect(treeInstance.values()).toContain('banana'); }); }); }); describe('selectionMode="follow"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'follow'}); + beforeEach(() => { + updateTree({selectionMode: 'follow'}); }); - it('should select only the clicked item with a simple click (like single select), deselecting others', async () => { + it('should select only the clicked item with a simple click (like single select), deselecting others', () => { const appleEl = getTreeItemElementByValue('apple')!; const bananaEl = getTreeItemElementByValue('banana')!; const carrotEl = getTreeItemElementByValue('carrot')!; ctrlClick(appleEl); ctrlClick(bananaEl); - expect(treeInstance.value()).toEqual(['apple', 'banana']); + expect(treeInstance.values()).toEqual(['apple', 'banana']); - await click(carrotEl); - expect(treeInstance.value()).toEqual(['carrot']); + click(carrotEl); + expect(treeInstance.values()).toEqual(['carrot']); - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); }); - it('should add to selection with ctrl+click and toggle individual items', async () => { + it('should add to selection with ctrl+click and toggle individual items', () => { const appleEl = getTreeItemElementByValue('apple')!; const berriesEl = getTreeItemElementByValue('berries')!; - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); ctrlClick(berriesEl); - expect(treeInstance.value()).toEqual(['apple', 'berries']); + expect(treeInstance.values()).toEqual(['apple', 'berries']); ctrlClick(appleEl); - expect(treeInstance.value()).toEqual(['berries']); + expect(treeInstance.values()).toEqual(['berries']); }); - it('should select a range with shift+click, anchoring from last selected/focused', async () => { + it('should select a range with shift+click, anchoring from last selected/focused', () => { const appleEl = getTreeItemElementByValue('apple')!; const berriesEl = getTreeItemElementByValue('berries')!; const carrotEl = getTreeItemElementByValue('carrot')!; const broccoliEl = getTreeItemElementByValue('broccoli')!; - await click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + click(appleEl); + expect(treeInstance.values()).toEqual(['apple']); shiftClick(carrotEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'apple', 'banana', 'berries', @@ -664,11 +588,11 @@ describe('Tree', () => { 'carrot', ]); - await click(berriesEl); - expect(treeInstance.value()).toEqual(['berries']); + click(berriesEl); + expect(treeInstance.values()).toEqual(['berries']); shiftClick(broccoliEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'berries', 'strawberry', 'blueberry', @@ -679,35 +603,35 @@ describe('Tree', () => { }); describe('selectable=false', () => { - it('should not select a range with shift+click if an item is not selectable', async () => { - await updateTreeItemByValue('banana', {selectable: false}); + it('should not select a range with shift+click if an item is not selectable', () => { + updateTreeItemByValue('banana', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; const berriesEl = getTreeItemElementByValue('berries')!; - await click(appleEl); + click(appleEl); shiftClick(berriesEl); - expect(treeInstance.value()).not.toContain('banana'); - expect(treeInstance.value()).toContain('apple'); - expect(treeInstance.value()).toContain('berries'); + expect(treeInstance.values()).not.toContain('banana'); + expect(treeInstance.values()).toContain('apple'); + expect(treeInstance.values()).toContain('berries'); }); - it('should not toggle selection of an item on simple click', async () => { - await updateTreeItemByValue('apple', {selectable: false}); + it('should not toggle selection of an item on simple click', () => { + updateTreeItemByValue('apple', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; - await click(appleEl); - expect(treeInstance.value()).not.toContain('apple'); + click(appleEl); + expect(treeInstance.values()).not.toContain('apple'); }); - it('should not add to selection with ctrl+click', async () => { - await updateTree({value: ['banana']}); - await updateTreeItemByValue('apple', {selectable: false}); + it('should not add to selection with ctrl+click', () => { + updateTree({values: ['banana']}); + updateTreeItemByValue('apple', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; ctrlClick(appleEl); - expect(treeInstance.value()).not.toContain('apple'); - expect(treeInstance.value()).toContain('banana'); + expect(treeInstance.values()).not.toContain('apple'); + expect(treeInstance.values()).toContain('banana'); }); }); }); @@ -716,168 +640,168 @@ describe('Tree', () => { describe('keyboard interactions', () => { describe('single select (multi=false)', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({multi: false}); + beforeEach(() => { + setupTestTree(); + updateTree({multi: false}); }); describe('selectionMode="explicit"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'explicit'}); + beforeEach(() => { + updateTree({selectionMode: 'explicit'}); }); - it('should select the focused item with Enter and deselect others', async () => { - await enter(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select the focused item with Enter and deselect others', () => { + enter(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - await enter(); - expect(treeInstance.value()).toEqual(['vegetables']); + down(); + enter(); + expect(treeInstance.values()).toEqual(['vegetables']); }); - it('should select the focused item with Space and deselect others', async () => { - await space(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select the focused item with Space and deselect others', () => { + space(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - await space(); - expect(treeInstance.value()).toEqual(['vegetables']); + down(); + space(); + expect(treeInstance.values()).toEqual(['vegetables']); }); - it('should move focus with arrows without changing selection until Enter/Space', async () => { - await enter(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should move focus with arrows without changing selection until Enter/Space', () => { + enter(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - expect(treeInstance.value()).toEqual(['fruits']); + down(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - expect(treeInstance.value()).toEqual(['fruits']); + down(); + expect(treeInstance.values()).toEqual(['fruits']); - await enter(); - expect(treeInstance.value()).toEqual(['grains']); + enter(); + expect(treeInstance.values()).toEqual(['grains']); }); describe('selectable=false', () => { - it('should not select the focused item with Enter', async () => { - await updateTreeItemByValue('fruits', {selectable: false}); - await enter(); - expect(treeInstance.value()).toEqual([]); + it('should not select the focused item with Enter', () => { + updateTreeItemByValue('fruits', {selectable: false}); + enter(); + expect(treeInstance.values()).toEqual([]); }); - it('should not select the focused item with Space', async () => { - await updateTreeItemByValue('fruits', {selectable: false}); - await space(); - expect(treeInstance.value()).toEqual([]); + it('should not select the focused item with Space', () => { + updateTreeItemByValue('fruits', {selectable: false}); + space(); + expect(treeInstance.values()).toEqual([]); }); }); }); describe('selectionMode="follow"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'follow'}); + beforeEach(() => { + updateTree({selectionMode: 'follow'}); }); - it('should select an item when it becomes focused with ArrowDown and deselect others', async () => { - await updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select an item when it becomes focused with ArrowDown and deselect others', () => { + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - expect(treeInstance.value()).toEqual(['vegetables']); + down(); + expect(treeInstance.values()).toEqual(['vegetables']); - await down(); - expect(treeInstance.value()).toEqual(['grains']); + down(); + expect(treeInstance.values()).toEqual(['grains']); }); - it('should select an item when it becomes focused with ArrowUp and deselect others', async () => { - await updateTree({value: ['grains']}); + it('should select an item when it becomes focused with ArrowUp and deselect others', () => { + updateTree({values: ['grains']}); - await up(); - expect(treeInstance.value()).toEqual(['vegetables']); + up(); + expect(treeInstance.values()).toEqual(['vegetables']); }); - it('should select the first item with Home and deselect others', async () => { - await updateTree({value: ['grains']}); - expect(treeInstance.value()).toEqual(['grains']); + it('should select the first item with Home and deselect others', () => { + updateTree({values: ['grains']}); + expect(treeInstance.values()).toEqual(['grains']); - await home(); - expect(treeInstance.value()).toEqual(['fruits']); + home(); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('should select the last visible item with End and deselect others', async () => { - await updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select the last visible item with End and deselect others', () => { + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); - await end(); - expect(treeInstance.value()).toEqual(['dairy']); + end(); + expect(treeInstance.values()).toEqual(['dairy']); }); - it('should select an item via typeahead and deselect others', async () => { - await updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select an item via typeahead and deselect others', () => { + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); - await type('V'); - expect(treeInstance.value()).toEqual(['vegetables']); + type('V'); + expect(treeInstance.values()).toEqual(['vegetables']); }); }); }); describe('multi select (multi=true)', () => { - beforeEach(async () => { - await setupTestTree(); - await updateTree({multi: true}); + beforeEach(() => { + setupTestTree(); + updateTree({multi: true}); }); describe('selectionMode="explicit"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'explicit'}); + beforeEach(() => { + updateTree({selectionMode: 'explicit'}); }); - it('should toggle selection of the focused item with Space, leaving other selections intact', async () => { - await space(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should toggle selection of the focused item with Space, leaving other selections intact', () => { + space(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - await space(); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + down(); + space(); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); }); - it('should move focus with arrows without changing selection', async () => { - await space(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should move focus with arrows without changing selection', () => { + space(); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - expect(treeInstance.value()).toEqual(['fruits']); + down(); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('should extend selection downwards with Shift+ArrowDown', async () => { - await shift(); - await down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + it('should extend selection downwards with Shift+ArrowDown', () => { + shift(); + down({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); - await down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains', 'vegetables']); + down({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains', 'vegetables']); }); - it('should extend selection upwards with Shift+ArrowUp', async () => { - await end(); - await shift(); - await up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['dairy', 'grains']); + it('should extend selection upwards with Shift+ArrowUp', () => { + end(); + shift(); + up({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['dairy', 'grains']); }); - it('Ctrl+A should select all enabled visible items, then deselect all', async () => { + it('Ctrl+A should select all enabled visible items, then deselect all', () => { // Expands vegetables and fruits - await down(); - await right(); - await up(); - await right(); + down(); + right(); + up(); + right(); - await updateTreeItemByValue('carrot', {disabled: true}); - await updateTreeItemByValue('broccoli', {disabled: true}); + updateTreeItemByValue('carrot', {disabled: true}); + updateTreeItemByValue('broccoli', {disabled: true}); - await keydown('A', {ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual([ + keydown('A', {ctrlKey: true}); + expect(treeInstance.values().sort()).toEqual([ 'apple', 'banana', 'berries', @@ -887,43 +811,43 @@ describe('Tree', () => { 'vegetables', ]); - await keydown('A', {ctrlKey: true}); - expect(treeInstance.value()).toEqual([]); + keydown('A', {ctrlKey: true}); + expect(treeInstance.values()).toEqual([]); }); - it('Ctrl+ArrowKey should move focus without changing selection', async () => { - await space(); - expect(treeInstance.value()).toEqual(['fruits']); + it('Ctrl+ArrowKey should move focus without changing selection', () => { + space(); + expect(treeInstance.values()).toEqual(['fruits']); - await down({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + down({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); - await up({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + up({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); }); describe('selectable=false', () => { - it('should not toggle selection of the focused item with Space', async () => { - await updateTreeItemByValue('fruits', {selectable: false}); - await space(); - expect(treeInstance.value()).toEqual([]); + it('should not toggle selection of the focused item with Space', () => { + updateTreeItemByValue('fruits', {selectable: false}); + space(); + expect(treeInstance.values()).toEqual([]); }); - it('should not extend selection with Shift+ArrowDown', async () => { - await updateTreeItemByValue('vegetables', {selectable: false}); - await shift(); - await down({shiftKey: true}); - await down({shiftKey: true}); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains']); + it('should not extend selection with Shift+ArrowDown', () => { + updateTreeItemByValue('vegetables', {selectable: false}); + shift(); + down({shiftKey: true}); + down({shiftKey: true}); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains']); }); - it('Ctrl+A should not select non-selectable items', async () => { - await expandAll(); - await updateTreeItemByValue('apple', {selectable: false}); - await updateTreeItemByValue('carrot', {selectable: false}); - await keydown('A', {ctrlKey: true}); - const value = treeInstance.value(); + it('Ctrl+A should not select non-selectable items', () => { + expandAll(); + updateTreeItemByValue('apple', {selectable: false}); + updateTreeItemByValue('carrot', {selectable: false}); + keydown('A', {ctrlKey: true}); + const value = treeInstance.values(); expect(value).not.toContain('apple'); expect(value).not.toContain('carrot'); expect(value).toContain('banana'); @@ -933,111 +857,111 @@ describe('Tree', () => { }); describe('selectionMode="follow"', () => { - beforeEach(async () => { - await updateTree({selectionMode: 'follow'}); + beforeEach(() => { + updateTree({selectionMode: 'follow'}); }); - it('should select the focused item and deselect others on ArrowDown', async () => { - await updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + it('should select the focused item and deselect others on ArrowDown', () => { + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); - await down(); - expect(treeInstance.value()).toEqual(['vegetables']); + down(); + expect(treeInstance.values()).toEqual(['vegetables']); }); - it('should select the focused item and deselect others on ArrowUp', async () => { - await updateTree({value: ['vegetables']}); - expect(treeInstance.value()).toEqual(['vegetables']); + it('should select the focused item and deselect others on ArrowUp', () => { + updateTree({values: ['vegetables']}); + expect(treeInstance.values()).toEqual(['vegetables']); - await up(); - expect(treeInstance.value()).toEqual(['fruits']); + up(); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('should move focus without changing selection on Ctrl+ArrowDown', async () => { - await updateTree({value: ['fruits']}); + it('should move focus without changing selection on Ctrl+ArrowDown', () => { + updateTree({values: ['fruits']}); expect(getFocusedTreeItemValue()).toBe('fruits'); - await down({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + down({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); expect(getFocusedTreeItemValue()).toBe('vegetables'); }); - it('should move focus without changing selection on Ctrl+ArrowUp', async () => { - await updateTree({value: ['fruits']}); + it('should move focus without changing selection on Ctrl+ArrowUp', () => { + updateTree({values: ['fruits']}); - await down({ctrlKey: true}); + down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); - await up({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + up({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); expect(getFocusedTreeItemValue()).toBe('fruits'); }); - it('should toggle selection of the focused item on Ctrl+Space, adding to existing selection', async () => { - await updateTree({value: ['fruits']}); - await down({ctrlKey: true}); + it('should toggle selection of the focused item on Ctrl+Space, adding to existing selection', () => { + updateTree({values: ['fruits']}); + down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); - await space({ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + space({ctrlKey: true}); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); - await space({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + space({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('should toggle selection of the focused item on Ctrl+Enter, adding to existing selection', async () => { - await updateTree({value: ['fruits']}); - await down({ctrlKey: true}); + it('should toggle selection of the focused item on Ctrl+Enter, adding to existing selection', () => { + updateTree({values: ['fruits']}); + down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); - await enter({ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + enter({ctrlKey: true}); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); - await enter({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + enter({ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('should extend selection downwards with Shift+ArrowDown', async () => { - await right(); // Expands fruits - await updateTree({value: ['fruits']}); + it('should extend selection downwards with Shift+ArrowDown', () => { + right(); // Expands fruits + updateTree({values: ['fruits']}); - await shift(); - await down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'fruits']); + shift(); + down({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['apple', 'fruits']); - await down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'banana', 'fruits']); + down({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['apple', 'banana', 'fruits']); }); - it('should extend selection upwards with Shift+ArrowUp', async () => { - await updateTree({value: ['grains']}); + it('should extend selection upwards with Shift+ArrowUp', () => { + updateTree({values: ['grains']}); - await shift(); - await up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['grains', 'vegetables']); + shift(); + up({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['grains', 'vegetables']); - await up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains', 'vegetables']); + up({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains', 'vegetables']); }); - it('should select a range with Shift+Space, anchoring from last selected/focused item', async () => { - await right(); // Expands fruits - await updateTree({value: ['fruits']}); + it('should select a range with Shift+Space, anchoring from last selected/focused item', () => { + right(); // Expands fruits + updateTree({values: ['fruits']}); - await down({ctrlKey: true}); - await down({ctrlKey: true}); + down({ctrlKey: true}); + down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('banana'); - await space({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'banana', 'fruits']); + space({shiftKey: true}); + expect(treeInstance.values().sort()).toEqual(['apple', 'banana', 'fruits']); }); - it('Ctrl+A: select all enabled visible items; second Ctrl+A deselects all except focused', async () => { - await right(); // Expands fruits - await updateTreeItemByValue('vegetables', {disabled: true}); + it('Ctrl+A: select all enabled visible items; second Ctrl+A deselects all except focused', () => { + right(); // Expands fruits + updateTreeItemByValue('vegetables', {disabled: true}); - await keydown('A', {ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual([ + keydown('A', {ctrlKey: true}); + expect(treeInstance.values().sort()).toEqual([ 'apple', 'banana', 'berries', @@ -1046,69 +970,68 @@ describe('Tree', () => { 'grains', ]); - await keydown('A', {ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + keydown('A', {ctrlKey: true}); + expect(treeInstance.values()).toEqual(['fruits']); }); - it('typeahead should select the focused item and deselect others', async () => { - await updateTree({value: ['fruits']}); - await type('V'); - expect(treeInstance.value()).toEqual(['vegetables']); + it('typeahead should select the focused item and deselect others', () => { + updateTree({values: ['fruits']}); + type('V'); + expect(treeInstance.values()).toEqual(['vegetables']); expect(getFocusedTreeItemValue()).toBe('vegetables'); }); describe('selectable=false', () => { - it('should not select an item on ArrowDown', async () => { - await updateTreeItemByValue('vegetables', {selectable: false}); - await down(); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value()).toEqual([]); + it('should not select an item on ArrowDown', () => { + updateTreeItemByValue('vegetables', {selectable: false}); + down(); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values()).toEqual([]); }); - it('should not toggle selection of the focused item on Ctrl+Space', async () => { - await updateTreeItemByValue('fruits', {selectable: false}); - await space({ctrlKey: true}); - expect(treeInstance.value()).toEqual([]); + it('should not toggle selection of the focused item on Ctrl+Space', () => { + updateTreeItemByValue('fruits', {selectable: false}); + space({ctrlKey: true}); + expect(treeInstance.values()).toEqual([]); }); - it('should not extend selection with Shift+ArrowDown', async () => { - await updateTreeItemByValue('vegetables', {selectable: false}); - await shift(); - await down({shiftKey: true}); - await down({shiftKey: true}); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains']); + it('should not extend selection with Shift+ArrowDown', () => { + updateTreeItemByValue('vegetables', {selectable: false}); + shift(); + down({shiftKey: true}); + down({shiftKey: true}); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains']); }); - it('typeahead should not select the focused item', async () => { - await updateTreeItemByValue('vegetables', {selectable: false}); - await type('v'); + it('typeahead should not select the focused item', () => { + updateTreeItemByValue('vegetables', {selectable: false}); + type('v'); expect(getFocusedTreeItemValue()).toBe('vegetables'); - expect(treeInstance.value()).not.toContain('vegetables'); + expect(treeInstance.values()).not.toContain('vegetables'); }); }); - it('should not select disabled items during Shift+ArrowKey navigation even if softDisabled is true', async () => { - await right(); // Expands fruits - await updateTreeItemByValue('banana', {disabled: true}); - await updateTree({value: ['apple'], softDisabled: true}); - await down(); // Focus moves to apple + it('should not select disabled items during Shift+ArrowKey navigation even if softDisabled is true', () => { + right(); // Expands fruits + updateTreeItemByValue('banana', {disabled: true}); + updateTree({values: ['apple'], softDisabled: true}); expect(getFocusedTreeItemValue()).toBe('apple'); - await keydown('Shift'); - await down({shiftKey: true}); + keydown('Shift'); + down({shiftKey: true}); expect(getFocusedTreeItemValue()).toBe('banana'); - expect(treeInstance.value().sort()).toEqual(['apple']); + expect(treeInstance.values().sort()).toEqual(['apple']); - await down({shiftKey: true}); // Focus 'berries' + down({shiftKey: true}); // Focus 'berries' expect(getFocusedTreeItemValue()).toBe('berries'); - expect(treeInstance.value().sort()).toEqual(['apple', 'berries']); + expect(treeInstance.values().sort()).toEqual(['apple', 'berries']); }); - it('should not change selection if tree is disabled', async () => { - await updateTree({value: ['fruits'], disabled: true}); - await down(); - expect(treeInstance.value()).toEqual(['fruits']); + it('should not change selection if tree is disabled', () => { + updateTree({values: ['fruits'], disabled: true}); + down(); + expect(treeInstance.values()).toEqual(['fruits']); }); }); }); @@ -1116,9 +1039,9 @@ describe('Tree', () => { }); describe('expansion and collapse', () => { - it('should expand items by setting expanded input', async () => { - await setupTestTree(); - await updateTree({ + it('should expand items by setting expanded input', () => { + setupTestTree(); + updateTree({ nodes: [ { value: 'fruits', @@ -1146,9 +1069,9 @@ describe('Tree', () => { expect(berriesEl.getAttribute('aria-expanded')).toBe('true'); }); - it('should not affect selected item when collapse', async () => { - await setupTestTree(); - await updateTree({ + it('should not affect selected item when collapse', () => { + setupTestTree(); + updateTree({ nodes: [ { value: 'fruits', @@ -1174,105 +1097,105 @@ describe('Tree', () => { const berriesEl = getTreeItemElementByValue('berries')!; const fruits = getTreeItemElementByValue('fruits')!; - await click(blueberryEl); - expect(treeInstance.value()).toEqual(['blueberry']); + click(blueberryEl); + expect(treeInstance.values()).toEqual(['blueberry']); - await left(); - await left(); // collapse berries + left(); + left(); // collapse berries expect(berriesEl.getAttribute('aria-expanded')).toBe('false'); - expect(treeInstance.value()).toEqual(['blueberry']); + expect(treeInstance.values()).toEqual(['blueberry']); - await left(); - await left(); // collapse fruits + left(); + left(); // collapse fruits expect(fruits.getAttribute('aria-expanded')).toBe('false'); - expect(treeInstance.value()).toEqual(['blueberry']); + expect(treeInstance.values()).toEqual(['blueberry']); }); describe('LTR', () => { - beforeEach(async () => { - await setupTestTree(); + beforeEach(() => { + setupTestTree(); }); describe('orientation="vertical"', () => { - beforeEach(async () => { - await updateTree({orientation: 'vertical'}); + beforeEach(() => { + updateTree({orientation: 'vertical'}); }); - it('should expand a collapsed item with ArrowRight', async () => { + it('should expand a collapsed item with ArrowRight', () => { const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await right(); + right(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); }); - it('should move focus to first child if ArrowRight on an expanded item', async () => { - await right(); // Expands fruits + it('should move focus to first child if ArrowRight on an expanded item', () => { + right(); // Expands fruits expect(getFocusedTreeItemValue()).toBe('fruits'); - await right(); + right(); expect(getFocusedTreeItemValue()).toBe('apple'); }); - it('should collapse an expanded item with ArrowLeft', async () => { - await right(); // Expands fruits + it('should collapse an expanded item with ArrowLeft', () => { + right(); // Expands fruits const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); - await left(); + left(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); - it('should move focus to parent if ArrowLeft on a collapsed non-root item', async () => { - await right(); // Expands fruits - await right(); // Focus apple (child of fruits) + it('should move focus to parent if ArrowLeft on a collapsed non-root item', () => { + right(); // Expands fruits + right(); // Focus apple (child of fruits) expect(getFocusedTreeItemValue()).toBe('apple'); - await left(); + left(); expect(getFocusedTreeItemValue()).toBe('fruits'); }); }); describe('orientation="horizontal"', () => { - beforeEach(async () => { - await updateTree({orientation: 'horizontal'}); + beforeEach(() => { + updateTree({orientation: 'horizontal'}); }); - it('should expand a collapsed item with ArrowDown', async () => { - await updateTree({orientation: 'horizontal'}); + it('should expand a collapsed item with ArrowDown', () => { + updateTree({orientation: 'horizontal'}); const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await down(); + down(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); }); - it('should move focus to first child if ArrowDown on an expanded item', async () => { - await updateTree({orientation: 'horizontal'}); + it('should move focus to first child if ArrowDown on an expanded item', () => { + updateTree({orientation: 'horizontal'}); expect(getFocusedTreeItemValue()).toBe('fruits'); - await down(); - await down(); + down(); + down(); expect(getFocusedTreeItemValue()).toBe('apple'); }); - it('should collapse an expanded item with ArrowUp', async () => { - await updateTree({orientation: 'horizontal'}); - await down(); // Expands fruits + it('should collapse an expanded item with ArrowUp', () => { + updateTree({orientation: 'horizontal'}); + down(); // Expands fruits const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); - await up(); + up(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); - it('should move focus to parent if ArrowUp on a collapsed non-root item', async () => { - await updateTree({orientation: 'horizontal'}); - await down(); // Expands fruits - await down(); + it('should move focus to parent if ArrowUp on a collapsed non-root item', () => { + updateTree({orientation: 'horizontal'}); + down(); // Expands fruits + down(); expect(getFocusedTreeItemValue()).toBe('apple'); - await up(); + up(); expect(getFocusedTreeItemValue()).toBe('fruits'); }); }); - it('should expand all sibling items with Shift + *', async () => { + it('should expand all sibling items with Shift + *', () => { const fruitsEl = getTreeItemElementByValue('fruits')!; const vegetablesEl = getTreeItemElementByValue('vegetables')!; const grainsEl = getTreeItemElementByValue('grains')!; @@ -1281,7 +1204,7 @@ describe('Tree', () => { expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); expect(vegetablesEl.getAttribute('aria-expanded')).toBe('false'); - await keydown('*', {shiftKey: true}); + keydown('*', {shiftKey: true}); expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); expect(vegetablesEl.getAttribute('aria-expanded')).toBe('true'); @@ -1289,90 +1212,90 @@ describe('Tree', () => { expect(dairyEl.hasAttribute('aria-expanded')).toBe(false); }); - it('should toggle expansion on pointerdown (click) for an expandable item', async () => { + it('should toggle expansion on pointerdown (click) for an expandable item', () => { const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await click(fruitsEl); + click(fruitsEl); expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); expect(getFocusedTreeItemValue()).toBe('fruits'); - await click(fruitsEl); + click(fruitsEl); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); - it('should not expand a non-expandable item on click', async () => { + it('should not expand a non-expandable item on click', () => { const grainsEl = getTreeItemElementByValue('grains')!; expect(grainsEl.hasAttribute('aria-expanded')).toBe(false); - await click(grainsEl); + click(grainsEl); expect(grainsEl.hasAttribute('aria-expanded')).toBe(false); expect(getFocusedTreeItemValue()).toBe('grains'); }); - it('should not expand a non-expandable item with expand key', async () => { + it('should not expand a non-expandable item with expand key', () => { const grainsEl = getTreeItemElementByValue('grains')!; - await down(); - await down(); + down(); + down(); expect(getFocusedTreeItemValue()).toBe('grains'); - await right(); + right(); expect(grainsEl.hasAttribute('aria-expanded')).toBe(false); expect(getFocusedTreeItemValue()).toBe('grains'); }); - it('should not expand/collapse if item is disabled', async () => { - await updateTreeItemByValue('fruits', {disabled: true}); + it('should not expand/collapse if item is disabled', () => { + updateTreeItemByValue('fruits', {disabled: true}); const fruitsEl = getTreeItemElementByValue('fruits')!; - await click(fruitsEl); + click(fruitsEl); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await right(); + right(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); - it('should not expand/collapse if tree is disabled', async () => { - await updateTree({disabled: true}); + it('should not expand/collapse if tree is disabled', () => { + updateTree({disabled: true}); const fruitsEl = getTreeItemElementByValue('fruits')!; - await click(fruitsEl); + click(fruitsEl); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await right(); + right(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); - it('should do nothing on collapseKey if item is collapsed and is a root item', async () => { + it('should do nothing on collapseKey if item is collapsed and is a root item', () => { const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); expect(getFocusedTreeItemValue()).toBe('fruits'); - await left(); + left(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); expect(getFocusedTreeItemValue()).toBe('fruits'); }); }); describe('RTL', () => { - beforeEach(async () => { - await setupTestTree('rtl'); + beforeEach(() => { + setupTestTree('rtl'); }); describe('orientation="vertical"', () => { - it('should expand a collapsed item with ArrowLeft', async () => { + it('should expand a collapsed item with ArrowLeft', () => { const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); - await left(); + left(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); }); - it('should collapse an expanded item with ArrowRight', async () => { - await left(); + it('should collapse an expanded item with ArrowRight', () => { + left(); const fruitsEl = getTreeItemElementByValue('fruits')!; expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); - await right(); + right(); expect(fruitsEl.getAttribute('aria-expanded')).toBe('false'); }); }); @@ -1385,235 +1308,216 @@ describe('Tree', () => { describe(`focusMode="${focusMode}"`, () => { describe('LTR', () => { - beforeEach(async () => { - await setupTestTree('ltr'); - await updateTree({focusMode}); + beforeEach(() => { + setupTestTree('ltr'); + updateTree({focusMode}); }); describe('vertical orientation', () => { - beforeEach(async () => { - await updateTree({orientation: 'vertical'}); + beforeEach(() => { + updateTree({orientation: 'vertical'}); }); - it('should move focus to the next visible item on ArrowDown', async () => { + it('should move focus to the next visible item on ArrowDown', () => { expect(isFocused('fruits')).toBe(true); - await right(); // Expands fruits - await down(); + right(); // Expands fruits + down(); expect(isFocused('apple')).toBe(true); - await down(); + down(); expect(isFocused('banana')).toBe(true); }); - it('should move focus to the previous visible item on ArrowUp', async () => { + it('should move focus to the previous visible item on ArrowUp', () => { expect(isFocused('fruits')).toBe(true); - await right(); // Expands fruits - await down(); - await down(); + right(); // Expands fruits + down(); + down(); expect(isFocused('banana')).toBe(true); - await up(); + up(); expect(isFocused('apple')).toBe(true); - await up(); + up(); expect(isFocused('fruits')).toBe(true); }); - it('should skip disabled items with ArrowDown if softDisabled=false', async () => { - await right(); // Expands fruits - await updateTreeItemByValue('apple', {disabled: true}); - await updateTree({softDisabled: false}); + it('should skip disabled items with ArrowDown if softDisabled=false', () => { + right(); // Expands fruits + updateTreeItemByValue('apple', {disabled: true}); + updateTree({softDisabled: false}); expect(isFocused('fruits')).toBe(true); - await down(); + down(); expect(isFocused('banana')).toBe(true); }); - it('should not skip disabled items with ArrowDown if softDisabled=true', async () => { - await right(); // Expands fruits - await updateTreeItemByValue('apple', {disabled: true}); - await updateTree({softDisabled: true}); + it('should not skip disabled items with ArrowDown if softDisabled=true', () => { + right(); // Expands fruits + updateTreeItemByValue('apple', {disabled: true}); + updateTree({softDisabled: true}); expect(isFocused('fruits')).toBe(true); - await down(); + down(); expect(isFocused('apple')).toBe(true); }); - it('should wrap focus from last to first with ArrowDown when wrap is true', async () => { - await updateTree({wrap: true}); - await end(); + it('should wrap focus from last to first with ArrowDown when wrap is true', () => { + updateTree({wrap: true}); + end(); expect(isFocused('dairy')).toBe(true); - await down(); + down(); expect(isFocused('fruits')).toBe(true); }); - it('should not wrap focus from last to first with ArrowDown when wrap is false', async () => { - await updateTree({wrap: false}); - await end(); + it('should not wrap focus from last to first with ArrowDown when wrap is false', () => { + updateTree({wrap: false}); + end(); expect(isFocused('dairy')).toBe(true); - await down(); + down(); expect(isFocused('dairy')).toBe(true); }); }); describe('horizontal orientation', () => { - beforeEach(async () => { - await updateTree({orientation: 'horizontal'}); + beforeEach(() => { + updateTree({orientation: 'horizontal'}); }); - it('should move focus to the next visible item on ArrowRight', async () => { + it('should move focus to the next visible item on ArrowRight', () => { expect(isFocused('fruits')).toBe(true); - await right(); + right(); expect(isFocused('vegetables')).toBe(true); }); - it('should move focus to the previous visible item on ArrowLeft', async () => { - await right(); + it('should move focus to the previous visible item on ArrowLeft', () => { + right(); expect(isFocused('vegetables')).toBe(true); - await left(); + left(); expect(isFocused('fruits')).toBe(true); }); }); - it('should move focus to the last enabled visible item on End (softDisabled="false")', async () => { - await updateTree({softDisabled: false}); - await right(); // Expands fruits - await updateTreeItemByValue('dairy', {disabled: true}); - await updateTreeItemByValue('grains', {disabled: true}); - await updateTreeItemByValue('vegetables', {disabled: true}); - await end(); + it('should move focus to the last enabled visible item on End (softDisabled="false")', () => { + updateTree({softDisabled: false}); + right(); // Expands fruits + updateTreeItemByValue('dairy', {disabled: true}); + updateTreeItemByValue('grains', {disabled: true}); + updateTreeItemByValue('vegetables', {disabled: true}); + end(); expect(isFocused('berries')).toBe(true); }); - it('should move focus to the first enabled visible item on Home (softDisabled="false")', async () => { - await updateTree({softDisabled: false}); - await end(); - await updateTreeItemByValue('fruits', {disabled: true}); - await home(); + it('should move focus to the first enabled visible item on Home (softDisabled="false")', () => { + updateTree({softDisabled: false}); + end(); + updateTreeItemByValue('fruits', {disabled: true}); + home(); expect(isFocused('vegetables')).toBe(true); }); }); describe('RTL', () => { - beforeEach(async () => { - await setupTestTree('rtl'); - await updateTree({focusMode}); + beforeEach(() => { + setupTestTree('rtl'); + updateTree({focusMode}); }); describe('vertical orientation', () => { - beforeEach(async () => { - await updateTree({orientation: 'vertical'}); + beforeEach(() => { + updateTree({orientation: 'vertical'}); }); - it('should move focus to the next visible item on ArrowDown', async () => { + it('should move focus to the next visible item on ArrowDown', () => { expect(isFocused('fruits')).toBe(true); - await down(); + down(); expect(isFocused('vegetables')).toBe(true); }); - it('should move focus to the previous visible item on ArrowUp', async () => { - await down(); + it('should move focus to the previous visible item on ArrowUp', () => { + down(); expect(isFocused('vegetables')).toBe(true); - await up(); + up(); expect(isFocused('fruits')).toBe(true); }); }); describe('horizontal orientation', () => { - beforeEach(async () => { - await updateTree({orientation: 'horizontal'}); + beforeEach(() => { + updateTree({orientation: 'horizontal'}); }); - it('should move focus to the next visible item on ArrowLeft', async () => { + it('should move focus to the next visible item on ArrowLeft', () => { expect(isFocused('fruits')).toBe(true); - await left(); + left(); expect(isFocused('vegetables')).toBe(true); }); - it('should move focus to the previous visible item on ArrowRight', async () => { - await left(); + it('should move focus to the previous visible item on ArrowRight', () => { + left(); expect(isFocused('vegetables')).toBe(true); - await right(); + right(); expect(isFocused('fruits')).toBe(true); }); }); }); describe('pointer navigation', () => { - beforeEach(async () => await setupTestTree()); + beforeEach(() => setupTestTree()); - it('should move focus to the clicked item', async () => { + it('should move focus to the clicked item', () => { const vegetablesEl = getTreeItemElementByValue('vegetables')!; - await click(vegetablesEl); + click(vegetablesEl); expect(isFocused('vegetables')).toBe(true); }); - it('should move focus to the clicked disabled item if softDisabled=true', async () => { - await updateTreeItemByValue('vegetables', {disabled: true}); - await updateTree({softDisabled: true}); + it('should move focus to the clicked disabled item if softDisabled=true', () => { + updateTreeItemByValue('vegetables', {disabled: true}); + updateTree({softDisabled: true}); const vegetablesEl = getTreeItemElementByValue('vegetables')!; - await click(vegetablesEl); + click(vegetablesEl); expect(isFocused('vegetables')).toBe(true); }); }); describe('typeahead functionality', () => { - beforeEach(async () => await setupTestTree()); // LTR by default + beforeEach(() => setupTestTree()); // LTR by default - it('should focus the first matching visible item when typing characters', async () => { - await right(); // Expands fruits - await type('Ba'); + it('should focus the first matching visible item when typing characters', () => { + right(); // Expands fruits + type('Ba'); expect(isFocused('banana')).toBe(true); }); - it('should select the focused item if selectionMode is "follow"', async () => { - await updateTree({selectionMode: 'follow'}); - await type('Gr'); + it('should select the focused item if selectionMode is "follow"', () => { + updateTree({selectionMode: 'follow'}); + type('Gr'); expect(isFocused('grains')).toBe(true); - expect(treeInstance.value()).toEqual(['grains']); + expect(treeInstance.values()).toEqual(['grains']); }); - it('should not select the focused item if selectionMode is "explicit"', async () => { - await updateTree({selectionMode: 'explicit'}); - await type('Gr'); + it('should not select the focused item if selectionMode is "explicit"', () => { + updateTree({selectionMode: 'explicit'}); + type('Gr'); expect(isFocused('grains')).toBe(true); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); - it('should skip disabled items with typeahead if softDisabled=false', async () => { - await right(); // Expands fruits - await updateTreeItemByValue('banana', {disabled: true}); - await updateTree({softDisabled: false}); - await type('B'); + it('should skip disabled items with typeahead if softDisabled=false', () => { + right(); // Expands fruits + updateTreeItemByValue('banana', {disabled: true}); + updateTree({softDisabled: false}); + type('B'); expect(isFocused('berries')).toBe(true); }); - it('should focus disabled items with typeahead if softDisabled=true', async () => { - await updateTreeItemByValue('vegetables', {disabled: true}); - await updateTree({softDisabled: true}); - await type('V'); + it('should focus disabled items with typeahead if softDisabled=true', () => { + updateTreeItemByValue('vegetables', {disabled: true}); + updateTree({softDisabled: true}); + type('V'); expect(isFocused('vegetables')).toBe(true); }); }); }); } }); - - describe('item mutations and focus stability', () => { - it('should recover focus by shifting to the default state if the active item is removed', async () => { - await setupTestTree(); - await updateTree({focusMode: 'activedescendant'}); - - const vegetablesEl = getTreeItemElementByValue('vegetables')!; - await click(vegetablesEl); - expect(getFocusedTreeItemValue()).toBe('vegetables'); - - const updatedNodes = testComponent.nodes().filter(n => n.value !== 'vegetables'); - testComponent.nodes.set(updatedNodes); - await fixture.whenStable(); - await waitForMicrotasks(); - defineTestVariables(); - - expect(getFocusedTreeItemValue()).toBe('fruits'); - }); - }); }); interface TestTreeNode { @@ -1636,10 +1540,9 @@ interface TestTreeNode { [softDisabled]="softDisabled()" [orientation]="orientation()" [disabled]="disabled()" - [(value)]="value" + [(values)]="values" [nav]="nav()" [currentType]="currentType()" - [tabIndex]="tabIndex()" #tree="ngTree" > @for (node of nodes(); track node.value) { @@ -1676,7 +1579,6 @@ interface TestTreeNode { `, imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestTreeComponent { nodes = signal([ @@ -1707,7 +1609,7 @@ class TestTreeComponent { {value: 'grains', label: 'Grains'}, {value: 'dairy', label: 'Dairy'}, ]); - value = signal([]); + values = signal([]); disabled = signal(false); orientation = signal<'vertical' | 'horizontal'>('vertical'); multi = signal(false); @@ -1717,29 +1619,4 @@ class TestTreeComponent { selectionMode = signal<'explicit' | 'follow'>('explicit'); nav = signal(false); currentType = signal('page' as 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'); - tabIndex = signal(undefined); } - -@Component({ - template: ` -
      -
    • Item 0
    • -
    • Item 0 Copy
    • -
    - `, - imports: [Tree, TreeItem], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class TreeWithDuplicateValues {} - -@Component({ - template: ` -
      -
    • Item 0
    • -
    • Item 1
    • -
    - `, - imports: [Tree, TreeItem], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class SingleSelectTreeWithMultipleValues {} diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index 0b759a0d4b12..d08cc870cbc0 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -7,30 +7,23 @@ */ import { - afterNextRender, + Directive, + ElementRef, afterRenderEffect, booleanAttribute, computed, - Directive, - ElementRef, inject, input, model, - OnDestroy, signal, - Signal, untracked, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; -import { - SortedCollection, - tabIndexTransform, - TreeItemPattern, - TreePattern, - reportViolations, -} from '../private'; +import {ComboboxTreePattern, TreeItemPattern, TreePattern} from '../private'; +import {ComboboxPopup} from '../combobox'; import type {TreeItem} from './tree-item'; +import {sortDirectives} from './utils'; /** * A container that transforms nested lists into an accessible, ARIA-compliant tree structure. @@ -64,6 +57,8 @@ import type {TreeItem} from './tree-item'; * * ``` * + * @developerPreview 21.0 + * * @see [Tree](guide/aria/tree) */ @Directive({ @@ -76,21 +71,27 @@ import type {TreeItem} from './tree-item'; '[attr.aria-multiselectable]': '_pattern.multi()', '[attr.aria-disabled]': '_pattern.disabled()', '[attr.aria-activedescendant]': '_pattern.activeDescendant()', - '[tabindex]': 'tabIndex() !== undefined ? tabIndex() : _pattern.tabIndex()', + '[tabindex]': '_pattern.tabIndex()', '(keydown)': '_pattern.onKeydown($event)', - '(click)': '_pattern.onClick($event)', - '(focusin)': '_pattern.onFocusIn()', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, + hostDirectives: [ComboboxPopup], }) -export class Tree implements OnDestroy { +export class Tree { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); /** A reference to the host element. */ readonly element = this._elementRef.nativeElement as HTMLElement; - /** The collection of tree items. */ - readonly _collection = new SortedCollection>(); + /** A reference to the parent combobox popup, if one exists. */ + private readonly _popup = inject>(ComboboxPopup, { + optional: true, + }); + + /** All TreeItem instances within this tree. */ + private readonly _unorderedItems = signal(new Set>()); /** A unique identifier for the tree. */ readonly id = input(inject(_IdGenerator).getId('ng-tree-', true)); @@ -130,14 +131,8 @@ export class Tree implements OnDestroy { /** The delay in seconds before the typeahead search is reset. */ readonly typeaheadDelay = input(500); - /** The tabindex of the tree. */ - readonly tabIndex = input(undefined, { - alias: 'tabindex', - transform: tabIndexTransform, - }); - /** The values of the currently selected items. */ - readonly value = model([]); + readonly values = model([]); /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; @@ -156,53 +151,77 @@ export class Tree implements OnDestroy { /** The UI pattern for the tree. */ readonly _pattern: TreePattern; - /** The ID of the active descendant in the tree. */ - readonly activeDescendant: Signal; + /** Whether the tree has received focus since it was rendered. */ + private _hasFocused = signal(false); constructor() { const inputs = { ...this, id: this.id, - items: computed(() => this._collection.orderedItems().map(item => item._pattern)), + items: computed(() => + [...this._unorderedItems()].sort(sortDirectives).map(item => item._pattern), + ), activeItem: signal | undefined>(undefined), + combobox: () => this._popup?.combobox?._pattern, element: () => this.element, }; - this._pattern = new TreePattern(inputs); + this._pattern = this._popup?.combobox + ? new ComboboxTreePattern(inputs) + : new TreePattern(inputs); + + if (this._popup?.combobox) { + this._popup?._controls?.set(this._pattern as ComboboxTreePattern); + } - this.activeDescendant = computed(() => this._pattern.activeDescendant()); + afterRenderEffect(() => { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + const violations = this._pattern.validate(); + for (const violation of violations) { + console.error(violation); + } + } + }); - afterNextRender(() => { - this._collection.startObserving(this.element); + afterRenderEffect(() => { + if (!this._hasFocused()) { + this._pattern.setDefaultState(); + } }); - // Check for any violations after the DOM has been updated. - if (typeof ngDevMode === 'undefined' || ngDevMode) { - afterRenderEffect({ - read: () => { - reportViolations(this._pattern.validate(), this.element); - }, - }); - } + afterRenderEffect(() => { + const items = inputs.items(); + const activeItem = untracked(() => inputs.activeItem()); - // Resets default focus based on selection state until interacted. - afterRenderEffect({write: () => this._pattern.setDefaultStateEffect()}); + if (!items.some(i => i === activeItem) && activeItem) { + this._pattern.treeBehavior.unfocus(); + } + }); - afterRenderEffect({ - write: () => { - const items = inputs.items(); - const activeItem = untracked(() => inputs.activeItem()); + afterRenderEffect(() => { + if (!(this._pattern instanceof ComboboxTreePattern)) return; - if (activeItem && !items.some(i => i === activeItem)) { - this._pattern.treeBehavior.unfocus(); - this._pattern.setDefaultState(); - } - }, + const items = inputs.items(); + const values = untracked(() => this.values()); + + if (items && values.some(v => !items.some(i => i.value() === v))) { + this.values.set(values.filter(v => items.some(i => i.value() === v))); + } }); } - ngOnDestroy() { - this._collection.stopObserving(); + _onFocus() { + this._hasFocused.set(true); + } + + _register(child: TreeItem) { + this._unorderedItems().add(child); + this._unorderedItems.set(new Set(this._unorderedItems())); + } + + _unregister(child: TreeItem) { + this._unorderedItems().delete(child); + this._unorderedItems.set(new Set(this._unorderedItems())); } scrollActiveItemIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { diff --git a/src/aria/private/utils/element.ts b/src/aria/tree/utils.ts similarity index 100% rename from src/aria/private/utils/element.ts rename to src/aria/tree/utils.ts diff --git a/src/cdk-experimental/column-resize/coalesced-style-scheduler.ts b/src/cdk-experimental/column-resize/coalesced-style-scheduler.ts index b67b20d9627a..35d107229320 100644 --- a/src/cdk-experimental/column-resize/coalesced-style-scheduler.ts +++ b/src/cdk-experimental/column-resize/coalesced-style-scheduler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, InjectionToken, NgZone, inject} from '@angular/core'; +import {Injectable, InjectionToken, NgZone, inject} from '@angular/core'; /** * @docs-private @@ -28,11 +28,14 @@ export const _COALESCED_STYLE_SCHEDULER = new InjectionToken<_CoalescedStyleSche * * @docs-private */ -@Service({autoProvided: false}) +@Injectable() export class _CoalescedStyleScheduler { private _currentSchedule: _Schedule | null = null; private _ngZone = inject(NgZone); + constructor(...args: unknown[]); + constructor() {} + /** * Schedules the specified task to run at the end of the current VM turn. */ diff --git a/src/cdk-experimental/column-resize/column-resize-notifier.ts b/src/cdk-experimental/column-resize/column-resize-notifier.ts index 51fc2e048f0d..53c9f0a4d7a5 100644 --- a/src/cdk-experimental/column-resize/column-resize-notifier.ts +++ b/src/cdk-experimental/column-resize/column-resize-notifier.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service} from '@angular/core'; +import {inject, Injectable} from '@angular/core'; import {Observable, Subject} from 'rxjs'; /** Indicates the width of a column. */ @@ -40,7 +40,7 @@ export interface ColumnSizeAction extends ColumnSize { * Originating source of column resize events within a table. * @docs-private */ -@Service({autoProvided: false}) +@Injectable() export class ColumnResizeNotifierSource { /** Emits when an in-progress resize is canceled. */ readonly resizeCanceled = new Subject(); @@ -53,7 +53,7 @@ export class ColumnResizeNotifierSource { } /** Service for triggering column resizes imperatively or being notified of them. */ -@Service({autoProvided: false}) +@Injectable() export class ColumnResizeNotifier { private readonly _source = inject(ColumnResizeNotifierSource); diff --git a/src/cdk-experimental/column-resize/column-size-store.ts b/src/cdk-experimental/column-resize/column-size-store.ts index baa8843089ef..6fe13e55f732 100644 --- a/src/cdk-experimental/column-resize/column-size-store.ts +++ b/src/cdk-experimental/column-resize/column-size-store.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; /** * Can be provided by the host application to enable persistence of column resize state. */ -@Service({autoProvided: false}) +@Injectable() export abstract class ColumnSizeStore { /** Returns the persisted size of the specified column in the specified table. */ abstract getSize(tableId: string, columnId: string): Observable | null; diff --git a/src/cdk-experimental/column-resize/event-dispatcher.ts b/src/cdk-experimental/column-resize/event-dispatcher.ts index a4afaf0affd0..52c16ed8f69d 100644 --- a/src/cdk-experimental/column-resize/event-dispatcher.ts +++ b/src/cdk-experimental/column-resize/event-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import {combineLatest, MonoTypeOperatorFunction, Observable, Subject} from 'rxjs'; import {distinctUntilChanged, map, share, skip, startWith, debounceTime} from 'rxjs/operators'; @@ -15,7 +15,7 @@ import {_closest} from '../popover-edit'; import {HEADER_ROW_SELECTOR} from './selectors'; /** Coordinates events between the column resize directives. */ -@Service({autoProvided: false}) +@Injectable() export class HeaderRowEventDispatcher { private readonly _ngZone = inject(NgZone); diff --git a/src/cdk-experimental/column-resize/resize-strategy.ts b/src/cdk-experimental/column-resize/resize-strategy.ts index 580188f32b27..be3928d3871c 100644 --- a/src/cdk-experimental/column-resize/resize-strategy.ts +++ b/src/cdk-experimental/column-resize/resize-strategy.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy, Provider, CSP_NONCE, inject, DOCUMENT} from '@angular/core'; +import {Injectable, OnDestroy, Provider, CSP_NONCE, inject, DOCUMENT} from '@angular/core'; import {coerceCssPixelValue} from '@angular/cdk/coercion'; import {CdkTable} from '@angular/cdk/table'; @@ -18,7 +18,7 @@ import {_COALESCED_STYLE_SCHEDULER, _CoalescedStyleScheduler} from './coalesced- * Provides an implementation for resizing a column. * The details of how resizing works for tables for flex mat-tables are quite different. */ -@Service({autoProvided: false}) +@Injectable() export abstract class ResizeStrategy implements OnDestroy { protected abstract readonly columnResize: ColumnResize; protected abstract readonly styleScheduler: _CoalescedStyleScheduler; @@ -115,7 +115,7 @@ export abstract class ResizeStrategy implements OnDestroy { * CSS selector w/ CSS variable * Updating all cell nodes */ -@Service({autoProvided: false}) +@Injectable() export class TableLayoutFixedResizeStrategy extends ResizeStrategy { protected readonly columnResize = inject(ColumnResize); protected readonly styleScheduler = inject<_CoalescedStyleScheduler>(_COALESCED_STYLE_SCHEDULER); @@ -161,7 +161,7 @@ export class TableLayoutFixedResizeStrategy extends ResizeStrategy { * CSS selector w/ CSS variable * Updating all mat-cell nodes */ -@Service({autoProvided: false}) +@Injectable() export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDestroy { protected readonly columnResize = inject(ColumnResize); protected readonly styleScheduler = inject<_CoalescedStyleScheduler>(_COALESCED_STYLE_SCHEDULER); diff --git a/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts b/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts index 591700a187f3..950fd0012305 100644 --- a/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts +++ b/src/cdk-experimental/popover-edit/edit-event-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import {combineLatest, MonoTypeOperatorFunction, Observable, pipe, Subject} from 'rxjs'; import { audit, @@ -48,7 +48,7 @@ export enum HoverContentState { /** * Service for sharing delegated events and state for triggering table edits. */ -@Service({autoProvided: false}) +@Injectable() export class EditEventDispatcher { private readonly _ngZone = inject(NgZone); diff --git a/src/cdk-experimental/popover-edit/edit-ref.ts b/src/cdk-experimental/popover-edit/edit-ref.ts index 01e5436a215f..6be33fee87aa 100644 --- a/src/cdk-experimental/popover-edit/edit-ref.ts +++ b/src/cdk-experimental/popover-edit/edit-ref.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy, afterNextRender, inject, Injector} from '@angular/core'; +import {Injectable, OnDestroy, afterNextRender, inject, Injector} from '@angular/core'; import {ControlContainer} from '@angular/forms'; import {Observable, Subject} from 'rxjs'; @@ -16,7 +16,7 @@ import {EditEventDispatcher} from './edit-event-dispatcher'; * Used for communication between the form within the edit lens and the * table that launched it. Provided by CdkEditControl within the lens. */ -@Service({autoProvided: false}) +@Injectable() export class EditRef implements OnDestroy { private readonly _form = inject(ControlContainer, {self: true}); private readonly _editEventDispatcher = diff --git a/src/cdk-experimental/popover-edit/edit-services.ts b/src/cdk-experimental/popover-edit/edit-services.ts index 2eb450a158ba..74454a4a0f56 100644 --- a/src/cdk-experimental/popover-edit/edit-services.ts +++ b/src/cdk-experimental/popover-edit/edit-services.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import {FocusTrapFactory} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling'; @@ -21,7 +21,7 @@ import {EditRef} from './edit-ref'; * number of services injected into each CdkPopoverEdit, this saves about 0.023ms of cpu time * and 56 bytes of memory per instance. */ -@Service({autoProvided: false}) +@Injectable() export class EditServices { readonly directionality = inject(Directionality); readonly editEventDispatcher = inject>>(EditEventDispatcher); diff --git a/src/cdk-experimental/popover-edit/focus-dispatcher.ts b/src/cdk-experimental/popover-edit/focus-dispatcher.ts index 5defca9af84d..e54f10f95639 100644 --- a/src/cdk-experimental/popover-edit/focus-dispatcher.ts +++ b/src/cdk-experimental/popover-edit/focus-dispatcher.ts @@ -8,7 +8,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {LEFT_ARROW, UP_ARROW, RIGHT_ARROW, DOWN_ARROW} from '@angular/cdk/keycodes'; -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; import {PartialObserver} from 'rxjs'; import { @@ -23,7 +23,7 @@ import {closest} from './polyfill'; * Service responsible for moving cell focus around in response to keyboard events. * May be overridden to customize the keyboard behavior of popover edit. */ -@Service() +@Injectable({providedIn: 'root'}) export class FocusDispatcher { protected readonly directionality = inject(Directionality); diff --git a/src/cdk-experimental/popover-edit/focus-escape-notifier.ts b/src/cdk-experimental/popover-edit/focus-escape-notifier.ts index f6bf344f0ca0..d25c5e57ec7d 100644 --- a/src/cdk-experimental/popover-edit/focus-escape-notifier.ts +++ b/src/cdk-experimental/popover-edit/focus-escape-notifier.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject, DOCUMENT, Injector} from '@angular/core'; +import {Injectable, NgZone, inject, DOCUMENT} from '@angular/core'; import {FocusTrap, InteractivityChecker} from '@angular/cdk/a11y'; import {Observable, Subject} from 'rxjs'; @@ -29,9 +29,8 @@ export class FocusEscapeNotifier extends FocusTrap { checker: InteractivityChecker, ngZone: NgZone, document: Document, - injector: Injector, ) { - super(element, checker, ngZone, document, true /* deferAnchors */, injector); + super(element, checker, ngZone, document, true /* deferAnchors */); // The focus trap adds "anchors" at the beginning and end of a trapped region that redirect // focus. We override that redirect behavior here with simply emitting on a stream. @@ -53,12 +52,11 @@ export class FocusEscapeNotifier extends FocusTrap { } /** Factory that allows easy instantiation of focus escape notifiers. */ -@Service() +@Injectable({providedIn: 'root'}) export class FocusEscapeNotifierFactory { private _checker = inject(InteractivityChecker); private _ngZone = inject(NgZone); private _document = inject(DOCUMENT); - private _injector = inject(Injector); /** * Creates a focus escape notifier region around the given element. @@ -66,12 +64,6 @@ export class FocusEscapeNotifierFactory { * @returns The created focus escape notifier instance. */ create(element: HTMLElement): FocusEscapeNotifier { - return new FocusEscapeNotifier( - element, - this._checker, - this._ngZone, - this._document, - this._injector, - ); + return new FocusEscapeNotifier(element, this._checker, this._ngZone, this._document); } } diff --git a/src/cdk-experimental/popover-edit/popover-edit.spec.ts b/src/cdk-experimental/popover-edit/popover-edit.spec.ts index 23940dce24f4..e5d1265981e2 100644 --- a/src/cdk-experimental/popover-edit/popover-edit.spec.ts +++ b/src/cdk-experimental/popover-edit/popover-edit.spec.ts @@ -10,9 +10,8 @@ import { ElementRef, ViewChild, inject, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {FormsModule, NgForm} from '@angular/forms'; import {BehaviorSubject} from 'rxjs'; @@ -42,39 +41,39 @@ describe('CDK Popover Edit', () => { let component: BaseTestComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(getComponentClass()); component = fixture.componentInstance; component.renderData(); fixture.detectChanges(); - await wait(50); + tick(10); fixture.detectChanges(); - }); + })); describe('row hover content', () => { - it('makes the first and last rows focusable but invisible', async () => { + it('makes the first and last rows focusable but invisible', fakeAsync(() => { const rows = component.getRows(); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(rows.length - 1)).toBe( HoverContentState.FOCUSABLE, ); - }); + })); - it('shows and hides on-hover content only after a delay', async () => { + it('shows and hides on-hover content only after a delay', fakeAsync(() => { const [row0, row1] = component.getRows(); row0.dispatchEvent(new Event('mouseover', {bubbles: true})); row0.dispatchEvent(new Event('mousemove', {bubbles: true})); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); - await wait(20); + tick(20); row0.dispatchEvent(new Event('mousemove', {bubbles: true})); - await wait(20); + tick(20); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); - await wait(50); + tick(31); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.ON); @@ -84,13 +83,13 @@ describe('CDK Popover Edit', () => { expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); - await wait(100); + tick(41); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.ON); - }); + })); - it('shows the hover content if the data changes after initialization', async () => { + it('shows the hover content if the data changes after initialization', fakeAsync(() => { fixture.componentInstance.renderData(); fixture.detectChanges(); @@ -98,129 +97,129 @@ describe('CDK Popover Edit', () => { row.dispatchEvent(new Event('mouseover', {bubbles: true})); row.dispatchEvent(new Event('mousemove', {bubbles: true})); - await wait(20); + tick(20); row.dispatchEvent(new Event('mousemove', {bubbles: true})); - await wait(50); + tick(50); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.ON); - }); + })); - it('shows hover content for the focused row and makes the rows above and below focusable', async () => { + it('shows hover content for the focused row and makes the rows above and below focusable', fakeAsync(() => { expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(2); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(4); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.getEditCell(4).blur(); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); - }); + })); - it('should close the focus content when pressing escape', async () => { + it('should close the focus content when pressing escape', fakeAsync(() => { expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); component.focusEditCell(2); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.ON); const event = new KeyboardEvent('keydown', {bubbles: true, key: 'Escape'}); component.getEditCell(2).dispatchEvent(event); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); - }); + })); it( 'shows hover content for the editing row and makes the rows above and below ' + 'focusable unless focus is in a different table row in which case it takes priority', - async () => { + fakeAsync(() => { expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); - await component.openLens(2); - await wait(50); + component.openLens(2); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(4); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); - }, + }), ); }); describe('triggering edit', () => { - it('opens edit from on-hover button', async () => { - await component.triggerHoverState(); + it('opens edit from on-hover button', fakeAsync(() => { + component.triggerHoverState(); component.clickOpenButton(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('opens edit from Enter on focued cell', async () => { + it('opens edit from Enter on focued cell', fakeAsync(() => { // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not trigger edit when disabled', async () => { + it('does not trigger edit when disabled', fakeAsync(() => { component.nameEditDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('sets aria label and role dialog on the popup', async () => { + it('sets aria label and role dialog on the popup', fakeAsync(() => { component.nameEditAriaLabel = 'Label of name!!'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.lensIsOpen()).toBe(true); const dialogElem = component.getEditPane()!; expect(dialogElem.getAttribute('aria-label')).toBe('Label of name!!'); expect(dialogElem.getAttribute('role')).toBe('dialog'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); }); describe('focus manipulation', () => { @@ -311,55 +310,55 @@ describe('CDK Popover Edit', () => { .querySelectorAll('input, button, .cdk-focus-trap-anchor'), ) as HTMLElement[]; - it('keeps focus within the lens by default', async () => { + it('keeps focus within the lens by default', fakeAsync(() => { // Open the name lens which has the default behavior. - await component.openLens(); + component.openLens(); fixture.detectChanges(); const focusableElements = getFocusablePaneElements(); // Focus the last element (end focus trap anchor). focusableElements[focusableElements.length - 1].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the top of the lens. expect(document.activeElement).toBe(focusableElements[1]); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('moves focus to the next cell when focus leaves end of lens with cdkPopoverEditTabOut', async () => { + it('moves focus to the next cell when focus leaves end of lens with cdkPopoverEditTabOut', fakeAsync(() => { // Open the weight lens which has tab out behavior. - await component.openLens(0, 2); + component.openLens(0, 2); const focusableElements = getFocusablePaneElements(); // Focus the last element (end focus trap anchor). focusableElements[focusableElements.length - 1].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the next editable cell. expect(document.activeElement).toBe(component.getEditCell(1, 1)); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it(`moves focus to the previous cell when focus leaves end of lens with -cdkPopoverEditTabOut`, async () => { +cdkPopoverEditTabOut`, fakeAsync(() => { // Open the weight lens which has tab out behavior. - await component.openLens(0, 2); + component.openLens(0, 2); const focusableElements = getFocusablePaneElements(); // Focus the first (start focus trap anchor). focusableElements[0].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the next editable cell. expect(document.activeElement).toBe(component.getEditCell(0, 1)); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); }); }); @@ -368,16 +367,16 @@ cdkPopoverEditTabOut`, async () => { expect(Math.floor(actual)).toBe(Math.floor(expected)); } - it('shows a lens with the value from the table', async () => { - await component.openLens(); + it('shows a lens with the value from the table', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); expect(component.getNameInput()!.value).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('positions the lens at the top left corner and spans the full width of the cell', async () => { - await component.openLens(); + it('positions the lens at the top left corner and spans the full width of the cell', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); const paneRect = component.getEditPane()!.getBoundingClientRect(); @@ -386,10 +385,10 @@ cdkPopoverEditTabOut`, async () => { expectPixelsToEqual(paneRect.width, cellRect.width); expectPixelsToEqual(paneRect.left, cellRect.left); expectPixelsToEqual(paneRect.top, cellRect.top); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('adjusts the positioning of the lens based on colspan', async () => { + it('adjusts the positioning of the lens based on colspan', fakeAsync(() => { const cellRects = getCells(getRows(component.table.nativeElement)[0]).map(cell => cell.getBoundingClientRect(), ); @@ -398,7 +397,7 @@ cdkPopoverEditTabOut`, async () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await component.openLens(); + component.openLens(); fixture.detectChanges(); let paneRect = component.getEditPane()!.getBoundingClientRect(); @@ -423,11 +422,11 @@ cdkPopoverEditTabOut`, async () => { expectPixelsToEqual(paneRect.top, cellRects[0].top); expectPixelsToEqual(paneRect.left, cellRects[0].left); expectPixelsToEqual(paneRect.right, cellRects[2].right); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('updates the form and submits, closing the lens', async () => { - await component.openLens(); + it('updates the form and submits, closing the lens', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -437,11 +436,11 @@ cdkPopoverEditTabOut`, async () => { fixture.detectChanges(); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydragon'); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on submit when form is invalid', async () => { - await component.openLens(); + it('does not close the lens on submit when form is invalid', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = ''; @@ -450,15 +449,15 @@ cdkPopoverEditTabOut`, async () => { component.clickSubmitButton(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'closes lens on submit when form is invalid with ' + 'cdkEditControlIgnoreSubmitUnlessValid = false', - async () => { + fakeAsync(() => { component.ignoreSubmitUnlessValid = false; - await component.openLens(); + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = ''; @@ -467,22 +466,22 @@ cdkPopoverEditTabOut`, async () => { component.clickSubmitButton(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); - it('closes the lens on close', async () => { - await component.openLens(); + it('closes the lens on close', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.clickCloseButton(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes and reopens a lens with modified value persisted', async () => { - await component.openLens(); + it('closes and reopens a lens with modified value persisted', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -494,15 +493,15 @@ cdkPopoverEditTabOut`, async () => { expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); expect(component.lensIsOpen()).toBe(false); - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.getNameInput()!.value).toBe('Hydragon'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('resets the lens to original value', async () => { - await component.openLens(); + it('resets the lens to original value', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -511,11 +510,11 @@ cdkPopoverEditTabOut`, async () => { component.clickRevertButton(); expect(component.getNameInput()!.value).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('should not reset the values when clicking revert without making changes', async () => { - await component.openLens(); + it('should not reset the values when clicking revert without making changes', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); expect(component.getNameInput()!.value).toBe('Hydrogen'); @@ -525,11 +524,11 @@ cdkPopoverEditTabOut`, async () => { expect(component.getNameInput()!.value).toBe('Hydrogen'); expect(component.getWeightInput()!.value).toBe('1.007'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('resets the lens to previously submitted value', async () => { - await component.openLens(); + it('resets the lens to previously submitted value', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -538,7 +537,7 @@ cdkPopoverEditTabOut`, async () => { component.clickSubmitButton(); fixture.detectChanges(); - await component.openLens(); + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon X'; @@ -547,11 +546,11 @@ cdkPopoverEditTabOut`, async () => { component.clickRevertButton(); expect(component.getNameInput()!.value).toBe('Hydragon'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes the lens on escape', async () => { - await component.openLens(); + it('closes the lens on escape', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); const event = new KeyboardEvent('keydown', {bubbles: true, key: 'Escape'}); @@ -560,11 +559,11 @@ cdkPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(event.preventDefault).toHaveBeenCalled(); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on escape with a modifier key', async () => { - await component.openLens(); + it('does not close the lens on escape with a modifier key', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); const event = new KeyboardEvent('keydown', {bubbles: true, key: 'Escape'}); @@ -575,21 +574,21 @@ cdkPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(true); expect(event.preventDefault).not.toHaveBeenCalled(); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on click within lens', async () => { - await component.openLens(); + it('does not close the lens on click within lens', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.dispatchEvent(new Event('click', {bubbles: true})); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes the lens on outside click', async () => { - await component.openLens(); + it('closes the lens on outside click', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -599,14 +598,14 @@ cdkPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'submits the lens on outside click with ' + 'cdkEditControlClickOutBehavior = "submit"', - async () => { + fakeAsync(() => { component.clickOutBehavior = 'submit'; - await component.openLens(); + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -616,15 +615,15 @@ cdkPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydragon'); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); it( 'does nothing on outside click with ' + 'cdkEditControlClickOutBehavior = "noop"', - async () => { + fakeAsync(() => { component.clickOutBehavior = 'noop'; - await component.openLens(); + component.openLens(); fixture.detectChanges(); component.getNameInput()!.value = 'Hydragon'; @@ -634,53 +633,53 @@ cdkPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(true); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); - it('sets focus on the first input in the lens', async () => { - await component.openLens(); + it('sets focus on the first input in the lens', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); expect(document.activeElement).toBe(component.getNameInput()); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('returns focus to the edited cell after closing', async () => { - await component.openLens(); + it('returns focus to the edited cell after closing', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.clickCloseButton(); expect(document.activeElement).toBe(component.getEditCell()); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'does not focus to the edited cell after closing if another element ' + 'outside the lens is already focused', - async () => { - await component.openLens(0); + fakeAsync(() => { + component.openLens(0); component.getEditCell(1).focus(); component.getEditCell(1).dispatchEvent(new Event('click', {bubbles: true})); expect(document.activeElement).toBe(component.getEditCell(1)); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); - it('should pass the directionality to the overlay', async () => { + it('should pass the directionality to the overlay', fakeAsync(() => { component.direction = 'rtl'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.getEditBoundingBox()!.getAttribute('dir')).toBe('rtl'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); }); }); } @@ -713,13 +712,13 @@ abstract class BaseTestComponent { element.name = form.value['name']; } - async triggerHoverState(rowIndex = 0) { + triggerHoverState(rowIndex = 0) { const row = getRows(this.table.nativeElement)[rowIndex]; row.dispatchEvent(new Event('mouseover', {bubbles: true})); row.dispatchEvent(new Event('mousemove', {bubbles: true})); // Wait for the mouse hover debounce in edit-event-dispatcher. - await wait(200); + tick(41); } getRows() { @@ -754,12 +753,12 @@ abstract class BaseTestComponent { this.getOpenButton(rowIndex)!.click(); } - async openLens(rowIndex = 0, cellIndex = 1) { + openLens(rowIndex = 0, cellIndex = 1) { this.focusEditCell(rowIndex, cellIndex); this.getEditCell(rowIndex, cellIndex).dispatchEvent( new KeyboardEvent('keydown', {bubbles: true, key: 'Enter'}), ); - await wait(50); + flush(); } getEditPane() { @@ -859,7 +858,6 @@ abstract class BaseTestComponent { `, imports: [CdkPopoverEditModule, FormsModule, CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VanillaTableOutOfCell extends BaseTestComponent { elements!: ChemicalElement[]; @@ -921,7 +919,6 @@ class VanillaTableOutOfCell extends BaseTestComponent { `, imports: [CdkPopoverEditModule, FormsModule, CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VanillaTableInCell extends BaseTestComponent { elements!: ChemicalElement[]; @@ -1007,7 +1004,6 @@ class ElementDataSource extends DataSource {
    `, imports: [CdkPopoverEditModule, FormsModule, CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkFlexTableInCell extends BaseTestComponent { displayedColumns = ['before', 'name', 'weight']; @@ -1082,7 +1078,6 @@ class CdkFlexTableInCell extends BaseTestComponent {
    `, imports: [CdkPopoverEditModule, FormsModule, CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTableInCell extends BaseTestComponent { displayedColumns = ['before', 'name', 'weight']; @@ -1164,7 +1159,6 @@ class CdkTableInCell extends BaseTestComponent {
    `, imports: [CdkPopoverEditModule, FormsModule, CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTableWithSkipRows extends BaseTestComponent { displayedColumns = ['before', 'name', 'weight']; @@ -1183,14 +1177,14 @@ describe('CDK Popover Edit - with focus ignore rows', () => { const dispatchKey = (cell: HTMLElement, keyCode: number) => dispatchKeyboardEvent(cell, 'keydown', keyCode); - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(CdkTableWithSkipRows); component = fixture.componentInstance; component.renderData(); fixture.detectChanges(); - await wait(50); + tick(10); fixture.detectChanges(); - }); + })); it('skips ignored rows when moving focus up', () => { const rows = component.getRows(); @@ -1245,10 +1239,6 @@ function getCells(row: Element): HTMLElement[] { // Common actions like mouse events and focus/blur cause timers to be fired off. // When not testing this behavior directly, use this function to clear any timers that were // created in passing. -async function clearLeftoverTimers() { - await wait(100); -} - -function wait(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); +function clearLeftoverTimers() { + tick(100); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index b0ac479cb231..e223747311e8 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -1,5 +1,5 @@ import {CdkVirtualScrollViewport, ScrollingModule} from '@angular/cdk/scrolling'; -import {Component, ViewChild, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild, ViewEncapsulation} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing'; import {ScrollingModule as ExperimentalScrollingModule} from './scrolling-module'; @@ -90,7 +90,6 @@ function finishInit(fixture: ComponentFixture) { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule, ExperimentalScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AutoSizeVirtualScroll { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; diff --git a/src/cdk-experimental/selection/selection-column.ts b/src/cdk-experimental/selection/selection-column.ts index ee3e8974c864..dbeaab3b09ee 100644 --- a/src/cdk-experimental/selection/selection-column.ts +++ b/src/cdk-experimental/selection/selection-column.ts @@ -13,6 +13,7 @@ import { OnDestroy, OnInit, ViewChild, + ChangeDetectionStrategy, ViewEncapsulation, inject, } from '@angular/core'; @@ -53,6 +54,7 @@ import {CdkSelectAll} from './select-all'; `, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [ CdkColumnDef, diff --git a/src/cdk-experimental/selection/selection.spec.ts b/src/cdk-experimental/selection/selection.spec.ts index 4b5bdd732392..5e8576063917 100644 --- a/src/cdk-experimental/selection/selection.spec.ts +++ b/src/cdk-experimental/selection/selection.spec.ts @@ -1,14 +1,7 @@ import {AsyncPipe} from '@angular/common'; import {CdkTableModule} from '@angular/cdk/table'; -import { - ChangeDetectorRef, - Component, - ElementRef, - ViewChild, - inject, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ChangeDetectorRef, Component, ElementRef, ViewChild, inject} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; import {CdkSelection} from './selection'; import {CdkSelectionModule} from './selection-module'; @@ -64,44 +57,44 @@ describe('CdkSelection', () => { }); describe('cdkSelectAll', () => { - it('should select all items when not all selected', async () => { + it('should select all items when not all selected', fakeAsync(() => { expect(component.cdkSelection.isAllSelected()).toBeFalsy(); expect(component.getSelectAll().textContent.trim()).toBe('unchecked'); - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.cdkSelection.isAllSelected()).toBeTruthy(); expect(component.getSelectAll().textContent.trim()).toBe('checked'); - }); + })); - it('should de-select all items when all selected', async () => { + it('should de-select all items when all selected', fakeAsync(() => { // Select all items. - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.cdkSelection.isAllSelected()).toBeTruthy(); expect(component.getSelectAll().textContent.trim()).toBe('checked'); - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.cdkSelection.isAllSelected()).toBeFalsy(); expect(component.getSelectAll().textContent.trim()).toBe('unchecked'); - }); + })); - it('should de-select all items when partially selected', async () => { + it('should de-select all items when partially selected', fakeAsync(() => { // make the 1st item selected. component.clickSelectionToggle(0); expect(component.cdkSelection.isPartialSelected()).toBeTruthy(); expect(component.getSelectAll().textContent.trim()).toBe('indeterminate'); - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.cdkSelection.isAllSelected()).toBeFalsy(); expect(component.cdkSelection.isPartialSelected()).toBeFalsy(); expect(component.getSelectAll().textContent.trim()).toBe('unchecked'); - }); + })); - it('should respond to selection toggle clicks', () => { + it('should respond to selection toggle clicks', fakeAsync(() => { // Start with no selection. expect(component.cdkSelection.isAllSelected()).toBeFalsy(); expect(component.getSelectAll().textContent.trim()).toBe('unchecked'); @@ -123,10 +116,10 @@ describe('CdkSelection', () => { expect(component.cdkSelection.isAllSelected()).toBeTruthy(); expect(component.cdkSelection.isPartialSelected()).toBeFalsy(); expect(component.getSelectAll().textContent.trim()).toBe('checked'); - }); + })); - it('should emit the correct selection change events', async () => { - await component.clickSelectAll(); + it('should emit the correct selection change events', fakeAsync(() => { + component.clickSelectAll(); expect(component.selectionChange!.before).toEqual([]); expect(component.selectionChange!.after).toEqual([ @@ -136,7 +129,7 @@ describe('CdkSelection', () => { {value: 'durian', index: 3}, ]); - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.selectionChange!.before).toEqual([ {value: 'apple', index: 0}, @@ -145,18 +138,18 @@ describe('CdkSelection', () => { {value: 'durian', index: 3}, ]); expect(component.selectionChange!.after).toEqual([]); - }); + })); }); describe('cdkSelectionToggle', () => { - it('should respond to select-all toggle click', async () => { + it('should respond to select-all toggle click', fakeAsync(() => { // All items not unchecked. expect(component.getSelectionToggle(0).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(1).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(2).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('unchecked'); - await component.clickSelectAll(); + component.clickSelectAll(); // Everything selected. expect(component.getSelectionToggle(0).textContent.trim()).toBe('checked'); @@ -164,16 +157,16 @@ describe('CdkSelection', () => { expect(component.getSelectionToggle(2).textContent.trim()).toBe('checked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('checked'); - await component.clickSelectAll(); + component.clickSelectAll(); // Everything unselected. expect(component.getSelectionToggle(0).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(1).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(2).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('unchecked'); - }); + })); - it('should select unselected item when clicked', () => { + it('should select unselected item when clicked', fakeAsync(() => { expect(component.cdkSelection.isSelected('apple', 0)).toBeFalsy(); expect(component.getSelectionToggle(0).textContent.trim()).toBe('unchecked'); @@ -189,11 +182,11 @@ describe('CdkSelection', () => { expect(component.getSelectionToggle(1).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(2).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('unchecked'); - }); + })); - it('should de-selected selected item when clicked', async () => { + it('should de-selected selected item when clicked', fakeAsync(() => { // Make all items selected. - await component.clickSelectAll(); + component.clickSelectAll(); component.clickSelectionToggle(1); @@ -207,9 +200,9 @@ describe('CdkSelection', () => { expect(component.getSelectionToggle(0).textContent.trim()).toBe('checked'); expect(component.getSelectionToggle(2).textContent.trim()).toBe('checked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('checked'); - }); + })); - it('should emit the correct selection change events', () => { + it('should emit the correct selection change events', fakeAsync(() => { component.clickSelectionToggle(1); expect(component.selectionChange!.before).toEqual([]); @@ -230,7 +223,7 @@ describe('CdkSelection', () => { {value: 'cherry', index: 2}, ]); expect(component.selectionChange!.after).toEqual([{value: 'banana', index: 1}]); - }); + })); }); }); @@ -244,7 +237,7 @@ describe('CdkSelection with multiple = false', () => { fixture.detectChanges(); }); - it('should uncheck the previous selection when selecting new item', () => { + it('should uncheck the previous selection when selecting new item', fakeAsync(() => { // Everything start as unchecked. expect(component.getSelectionToggle(0).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(1).textContent.trim()).toBe('unchecked'); @@ -273,9 +266,9 @@ describe('CdkSelection with multiple = false', () => { expect(component.getSelectionToggle(1).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(2).textContent.trim()).toBe('unchecked'); expect(component.getSelectionToggle(3).textContent.trim()).toBe('unchecked'); - }); + })); - it('should emit the correct selection change events', () => { + it('should emit the correct selection change events', fakeAsync(() => { component.clickSelectionToggle(1); expect(component.selectionChange!.before).toEqual([]); @@ -290,19 +283,19 @@ describe('CdkSelection with multiple = false', () => { expect(component.selectionChange!.before).toEqual([{value: 'cherry', index: 2}]); expect(component.selectionChange!.after).toEqual([]); - }); + })); }); describe('cdkSelectionColumn', () => { let fixture: ComponentFixture; let component: MultiSelectTableWithSelectionColumn; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(MultiSelectTableWithSelectionColumn); component = fixture.componentInstance; fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); it('should show check boxes', () => { const checkboxes = @@ -311,23 +304,23 @@ describe('cdkSelectionColumn', () => { expect(checkboxes.length).toBe(5); }); - it('should allow select all', async () => { + it('should allow select all', fakeAsync(() => { expect(component.getSelectAll().checked).toBe(false); expect(component.getSelectionToggle(0).checked).toBe(false); expect(component.getSelectionToggle(1).checked).toBe(false); expect(component.getSelectionToggle(2).checked).toBe(false); expect(component.getSelectionToggle(3).checked).toBe(false); - await component.clickSelectAll(); + component.clickSelectAll(); expect(component.getSelectAll().checked).toBe(true); expect(component.getSelectionToggle(0).checked).toBe(true); expect(component.getSelectionToggle(1).checked).toBe(true); expect(component.getSelectionToggle(2).checked).toBe(true); expect(component.getSelectionToggle(3).checked).toBe(true); - }); + })); - it('should allow toggle rows', () => { + it('should allow toggle rows', fakeAsync(() => { expect(component.getSelectAll().checked).toBe(false); expect(component.getSelectAll().indeterminate).toBe(false); expect(component.getSelectionToggle(0).checked).toBe(false); @@ -347,10 +340,10 @@ describe('cdkSelectionColumn', () => { expect(component.getSelectionToggle(1).checked).toBe(true); expect(component.getSelectionToggle(2).checked).toBe(true); expect(component.getSelectionToggle(3).checked).toBe(true); - }); + })); describe('cdkRowSelection', () => { - it('should set .cdk-selected on selected rows', () => { + it('should set .cdk-selected on selected rows', fakeAsync(() => { expect(component.getRow(0).classList.contains('cdk-selected')).toBeFalsy(); expect(component.getRow(1).classList.contains('cdk-selected')).toBeFalsy(); expect(component.getRow(2).classList.contains('cdk-selected')).toBeFalsy(); @@ -363,9 +356,9 @@ describe('cdkSelectionColumn', () => { component.clickSelectionToggle(0); expect(component.getRow(0).classList.contains('cdk-selected')).toBeFalsy(); - }); + })); - it('should set aria-selected on selected rows', () => { + it('should set aria-selected on selected rows', fakeAsync(() => { expect(component.getRow(0).getAttribute('aria-selected')).toBe('false'); expect(component.getRow(1).getAttribute('aria-selected')).toBe('false'); expect(component.getRow(2).getAttribute('aria-selected')).toBe('false'); @@ -378,7 +371,7 @@ describe('cdkSelectionColumn', () => { component.clickSelectionToggle(0); expect(component.getRow(0).getAttribute('aria-selected')).toBe('false'); - }); + })); }); }); @@ -396,7 +389,7 @@ describe('cdkSelectionColumn with multiple = false', () => { expect(component.elementRef.nativeElement.querySelector('input[cdkselectall]')).toBe(null); }); - it('should allow selecting one single row', () => { + it('should allow selecting one single row', fakeAsync(() => { expect(component.getSelectionToggle(0).checked).toBe(false); expect(component.getSelectionToggle(1).checked).toBe(false); expect(component.getSelectionToggle(2).checked).toBe(false); @@ -413,13 +406,9 @@ describe('cdkSelectionColumn with multiple = false', () => { component.clickSelectionToggle(1); expect(component.getSelectionToggle(1).checked).toBe(false); - }); + })); }); -function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - @Component({ template: `
      `, imports: [CdkSelectionModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithMultiSelection { private readonly _elementRef = inject(ElementRef); @@ -462,9 +450,9 @@ class ListWithMultiSelection { } } - async clickSelectAll() { + clickSelectAll() { this.getSelectAll().click(); - await wait(0); + flush(); this._cdr.detectChanges(); } @@ -475,6 +463,7 @@ class ListWithMultiSelection { } toggle.click(); + flush(); this._cdr.detectChanges(); } @@ -504,7 +493,6 @@ class ListWithMultiSelection { }
    `, imports: [CdkSelectionModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithSingleSelection { private readonly _elementRef = inject(ElementRef); @@ -522,6 +510,7 @@ class ListWithSingleSelection { } toggle.click(); + flush(); this._cdr.detectChanges(); } @@ -545,7 +534,6 @@ class ListWithSingleSelection { `, imports: [CdkSelectionModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiSelectTableWithSelectionColumn { readonly elementRef = inject(ElementRef); @@ -566,10 +554,11 @@ class MultiSelectTableWithSelectionColumn { } } - async clickSelectAll() { + clickSelectAll() { this.getSelectAll().click(); - await wait(0); + flush(); this._cdr.detectChanges(); + flush(); } clickSelectionToggle(index: number) { @@ -579,7 +568,9 @@ class MultiSelectTableWithSelectionColumn { } toggle.click(); + flush(); this._cdr.detectChanges(); + flush(); } getSelectAll(): HTMLInputElement { @@ -610,7 +601,6 @@ class MultiSelectTableWithSelectionColumn { `, imports: [CdkSelectionModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SingleSelectTableWithSelectionColumn { readonly elementRef = inject(ElementRef); @@ -628,7 +618,9 @@ class SingleSelectTableWithSelectionColumn { } toggle.click(); + flush(); this._cdr.detectChanges(); + flush(); } getSelectionToggle(index: number): HTMLInputElement { diff --git a/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts b/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts index ec48d1146064..255401cd7e99 100644 --- a/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts +++ b/src/cdk-experimental/table-scroll-container/table-scroll-container.spec.ts @@ -1,7 +1,7 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {Platform} from '@angular/cdk/platform'; import {CdkTable, CdkTableModule} from '@angular/cdk/table'; -import {Component, Type, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Type, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {BehaviorSubject, combineLatest} from 'rxjs'; import {map} from 'rxjs/operators'; @@ -294,7 +294,6 @@ class FakeDataSource extends DataSource { box-sizing: border-box; } `, - changeDetection: ChangeDetectionStrategy.Eager, }) class StickyNativeLayoutCdkTableApp { dataSource: FakeDataSource = new FakeDataSource(); diff --git a/src/cdk/BUILD.bazel b/src/cdk/BUILD.bazel index 42abfe733e5b..252314d2d142 100644 --- a/src/cdk/BUILD.bazel +++ b/src/cdk/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") load("@npm//:defs.bzl", "npm_link_all_packages") load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS", "CDK_ENTRYPOINTS_WITH_STYLES", "CDK_SCSS_LIBS", "CDK_TARGETS") load("//tools:defaults.bzl", "ng_package", "sass_library", "ts_project") diff --git a/src/cdk/a11y/a11y.md b/src/cdk/a11y/a11y.md index b436a1820b2c..ac989804d0aa 100644 --- a/src/cdk/a11y/a11y.md +++ b/src/cdk/a11y/a11y.md @@ -87,7 +87,7 @@ be used to create accessible experience for components like [modal dialogs](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), where focus must be constrained. -Import `CdkTrapFocus` in your component to use this directive. +This directive is declared in `A11yModule`. #### Example @@ -139,11 +139,10 @@ for more information on aria-live regions. ```ts @Component({...}) export class MyComponent { - private liveAnnouncer = inject(LiveAnnouncer); - announceMessage() { - this.liveAnnouncer.announce("Hey Google"); - } + constructor(liveAnnouncer: LiveAnnouncer) { + liveAnnouncer.announce("Hey Google"); + } } ``` diff --git a/src/cdk/a11y/aria-describer/aria-describer.spec.ts b/src/cdk/a11y/aria-describer/aria-describer.spec.ts index 2dc7403287e8..d3f73167b10e 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.spec.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.spec.ts @@ -1,6 +1,7 @@ -import {AriaDescriber, CDK_DESCRIBEDBY_HOST_ATTRIBUTE} from './aria-describer'; +import {A11yModule, CDK_DESCRIBEDBY_HOST_ATTRIBUTE} from '../index'; +import {AriaDescriber} from './aria-describer'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {Component, ElementRef, ViewChild, inject, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, ViewChild, inject} from '@angular/core'; describe('AriaDescriber', () => { let ariaDescriber: AriaDescriber; @@ -394,7 +395,7 @@ function expectMessage(el: Element, message: string) {
    Hello
    Hey
    `, - changeDetection: ChangeDetectionStrategy.Eager, + imports: [A11yModule], }) class TestApp { ariaDescriber = inject(AriaDescriber); diff --git a/src/cdk/a11y/aria-describer/aria-describer.ts b/src/cdk/a11y/aria-describer/aria-describer.ts index 82782cbb7c6b..85118572193e 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy, APP_ID, inject, DOCUMENT} from '@angular/core'; +import {Injectable, OnDestroy, APP_ID, inject, DOCUMENT} from '@angular/core'; import {Platform} from '../../platform'; import {addAriaReferencedId, getAriaReferenceIds, removeAriaReferencedId} from './aria-reference'; import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '../../private'; @@ -23,10 +23,25 @@ export interface RegisteredMessage { referenceCount: number; } -/** ID prefix used for each created message element. */ -const CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message'; +/** + * ID used for the body container where all messages are appended. + * @deprecated No longer being used. To be removed. + * @breaking-change 14.0.0 + */ +export const MESSAGES_CONTAINER_ID = 'cdk-describedby-message-container'; + +/** + * ID prefix used for each created message element. + * @deprecated To be turned into a private variable. + * @breaking-change 14.0.0 + */ +export const CDK_DESCRIBEDBY_ID_PREFIX = 'cdk-describedby-message'; -/** Attribute given to each host element that is described by a message element. */ +/** + * Attribute given to each host element that is described by a message element. + * @deprecated To be turned into a private variable. + * @breaking-change 14.0.0 + */ export const CDK_DESCRIBEDBY_HOST_ATTRIBUTE = 'cdk-describedby-host'; /** Global incremental identifier for each registered message element. */ @@ -37,7 +52,7 @@ let nextId = 0; * want to use aria-describedby to further describe themselves without adding additional visual * content. */ -@Service() +@Injectable({providedIn: 'root'}) export class AriaDescriber implements OnDestroy { private _platform = inject(Platform); private _document = inject(DOCUMENT); @@ -51,6 +66,8 @@ export class AriaDescriber implements OnDestroy { /** Unique ID for the service. */ private readonly _id = `${nextId++}`; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); this._id = inject(APP_ID) + '-' + nextId++; diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts index 68db81ab7ae8..ee2955d28b1f 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts @@ -1,7 +1,7 @@ import {TAB} from '../../keycodes'; import {Platform} from '../../platform'; -import {Component, ViewChild, DOCUMENT, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ViewChild, DOCUMENT} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import { createMouseEvent, @@ -20,10 +20,6 @@ import { FocusOrigin, } from './focus-monitor'; -function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - describe('FocusMonitor', () => { let fixture: ComponentFixture; let buttonElement: HTMLElement; @@ -74,23 +70,23 @@ describe('FocusMonitor', () => { patchElementFocus(buttonElement); }); - it('manually registered element should receive focus classes', async () => { + it('manually registered element should receive focus classes', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.contains('cdk-focused')) .withContext('button should have cdk-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledTimes(1); - }); + })); - it('should detect focus via keyboard', async () => { + it('should detect focus via keyboard', fakeAsync(() => { // Simulate focus via keyboard. dispatchKeyboardEvent(document, 'keydown', TAB); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -102,14 +98,14 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-keyboard-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('keyboard'); - }); + })); - it('should detect focus via mouse', async () => { + it('should detect focus via mouse', fakeAsync(() => { // Simulate focus via mouse. dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -121,14 +117,14 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-mouse-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('mouse'); - }); + })); - it('should detect focus via touch', async () => { + it('should detect focus via touch', fakeAsync(() => { // Simulate focus via touch. dispatchFakeEvent(buttonElement, 'touchstart'); buttonElement.focus(); fixture.detectChanges(); - await wait(TOUCH_BUFFER_MS); + tick(TOUCH_BUFFER_MS); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -140,13 +136,13 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-touch-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('touch'); - }); + })); - it('should detect programmatic focus', async () => { + it('should detect programmatic focus', fakeAsync(() => { // Programmatically focus. buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -158,9 +154,9 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-program-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('program'); - }); + })); - it('should detect fake mousedown from a screen reader on Chrome', async () => { + it('should detect fake mousedown from a screen reader on Chrome', fakeAsync(() => { // Simulate focus via a fake mousedown from a screen reader. dispatchMouseEvent(buttonElement, 'mousedown'); const event = createMouseEvent('mousedown'); @@ -169,7 +165,7 @@ describe('FocusMonitor', () => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -181,9 +177,9 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-keyboard-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('keyboard'); - }); + })); - it('should detect fake mousedown from a screen reader on Firefox', async () => { + it('should detect fake mousedown from a screen reader on Firefox', fakeAsync(() => { // Simulate focus via a fake mousedown from a screen reader. dispatchMouseEvent(buttonElement, 'mousedown'); const event = createMouseEvent('mousedown'); @@ -192,7 +188,7 @@ describe('FocusMonitor', () => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -204,11 +200,11 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-keyboard-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('keyboard'); - }); + })); - it('focusVia keyboard should simulate keyboard focus', async () => { + it('focusVia keyboard should simulate keyboard focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'keyboard'); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -220,12 +216,12 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-keyboard-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('keyboard'); - }); + })); - it('focusVia mouse should simulate mouse focus', async () => { + it('focusVia mouse should simulate mouse focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'mouse'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -237,12 +233,12 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-mouse-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('mouse'); - }); + })); - it('focusVia touch should simulate touch focus', async () => { + it('focusVia touch should simulate touch focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'touch'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -254,12 +250,12 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-touch-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('touch'); - }); + })); - it('focusVia program should simulate programmatic focus', async () => { + it('focusVia program should simulate programmatic focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'program'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -271,12 +267,12 @@ describe('FocusMonitor', () => { .withContext('button should have cdk-program-focused class') .toBe(true); expect(changeHandler).toHaveBeenCalledWith('program'); - }); + })); - it('should remove focus classes on blur', async () => { + it('should remove focus classes on blur', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -292,12 +288,12 @@ describe('FocusMonitor', () => { .withContext('button should not have any focus classes') .toBe(0); expect(changeHandler).toHaveBeenCalledWith(null); - }); + })); - it('should remove classes on stopMonitoring', async () => { + it('should remove classes on stopMonitoring', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -309,12 +305,12 @@ describe('FocusMonitor', () => { expect(buttonElement.classList.length) .withContext('button should not have any focus classes') .toBe(0); - }); + })); - it('should remove classes when destroyed', async () => { + it('should remove classes when destroyed', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -327,27 +323,27 @@ describe('FocusMonitor', () => { expect(buttonElement.classList.length) .withContext('button should not have any focus classes') .toBe(0); - }); + })); - it('should pass focus options to the native focus method', async () => { + it('should pass focus options to the native focus method', fakeAsync(() => { spyOn(buttonElement, 'focus'); focusMonitor.focusVia(buttonElement, 'program', {preventScroll: true}); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.focus).toHaveBeenCalledWith( jasmine.objectContaining({ preventScroll: true, }), ); - }); + })); - it('should not clear the focus origin too early in the current event loop', async () => { + it('should not clear the focus origin too early in the current event loop', fakeAsync(() => { dispatchKeyboardEvent(document, 'keydown', TAB); // Simulate the behavior of Firefox 57 where the focus event sometimes happens *one* tick later. - await fixture.whenStable(); + tick(); buttonElement.focus(); @@ -355,19 +351,19 @@ describe('FocusMonitor', () => { // focus origin should be reported properly. expect(changeHandler).toHaveBeenCalledWith('keyboard'); - await fixture.whenStable(); - }); + flush(); + })); - it('should clear the focus origin after one tick with "immediate" detection', async () => { + it('should clear the focus origin after one tick with "immediate" detection', fakeAsync(() => { dispatchKeyboardEvent(document, 'keydown', TAB); - await wait(2); + tick(2); buttonElement.focus(); // After 2 ticks, the timeout has cleared the origin. Default is 'program'. expect(changeHandler).toHaveBeenCalledWith('program'); - }); + })); - it('should check children if monitor was called with different checkChildren', async () => { + it('should check children if monitor was called with different checkChildren', fakeAsync(() => { const parent = fixture.nativeElement.querySelector('.parent'); focusMonitor.monitor(parent, true); @@ -377,16 +373,16 @@ describe('FocusMonitor', () => { dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(parent.classList).toContain('cdk-focused'); expect(parent.classList).toContain('cdk-mouse-focused'); - }); + })); - it('focusVia should change the focus origin when called on the focused node', async () => { + it('focusVia should change the focus origin when called on the focused node', fakeAsync(() => { spyOn(buttonElement, 'focus').and.callThrough(); focusMonitor.focusVia(buttonElement, 'keyboard'); - await fixture.whenStable(); + flush(); fakeActiveElement = buttonElement; expect(buttonElement.classList.length) @@ -403,7 +399,7 @@ describe('FocusMonitor', () => { expect(buttonElement.focus).toHaveBeenCalledTimes(1); focusMonitor.focusVia(buttonElement, 'mouse'); - await fixture.whenStable(); + flush(); fakeActiveElement = buttonElement; expect(buttonElement.classList.length) @@ -418,15 +414,15 @@ describe('FocusMonitor', () => { expect(changeHandler).toHaveBeenCalledTimes(2); expect(changeHandler).toHaveBeenCalledWith('mouse'); expect(buttonElement.focus).toHaveBeenCalledTimes(1); - }); + })); - it('focusVia should change the focus origin when called a focused child node', async () => { + it('focusVia should change the focus origin when called a focused child node', fakeAsync(() => { const parent = fixture.nativeElement.querySelector('.parent'); focusMonitor.stopMonitoring(buttonElement); // The button gets monitored by default. focusMonitor.monitor(parent, true).subscribe(changeHandler); spyOn(buttonElement, 'focus').and.callThrough(); focusMonitor.focusVia(buttonElement, 'keyboard'); - await fixture.whenStable(); + flush(); fakeActiveElement = buttonElement; expect(parent.classList.length) @@ -443,7 +439,7 @@ describe('FocusMonitor', () => { expect(buttonElement.focus).toHaveBeenCalledTimes(1); focusMonitor.focusVia(buttonElement, 'mouse'); - await fixture.whenStable(); + flush(); fakeActiveElement = buttonElement; expect(parent.classList.length) @@ -458,7 +454,7 @@ describe('FocusMonitor', () => { expect(changeHandler).toHaveBeenCalledTimes(2); expect(changeHandler).toHaveBeenCalledWith('mouse'); expect(buttonElement.focus).toHaveBeenCalledTimes(1); - }); + })); }); describe('FocusMonitor with "eventual" detection', () => { @@ -490,14 +486,14 @@ describe('FocusMonitor with "eventual" detection', () => { patchElementFocus(buttonElement); }); - it('should not clear the focus origin, even after a few seconds', async () => { + it('should not clear the focus origin, even after a few seconds', fakeAsync(() => { dispatchKeyboardEvent(document, 'keydown', TAB); - await wait(2000); + tick(2000); buttonElement.focus(); expect(changeHandler).toHaveBeenCalledWith('keyboard'); - }); + })); }); describe('cdkMonitorFocus', () => { @@ -514,18 +510,18 @@ describe('cdkMonitorFocus', () => { patchElementFocus(buttonElement); }); - it('should initially not be focused (directive)', () => { + it('should initially not be focused', () => { expect(buttonElement.classList.length) .withContext('button should not have focus classes') .toBe(0); }); - it('should detect focus via keyboard (directive)', async () => { + it('should detect focus via keyboard', fakeAsync(() => { // Simulate focus via keyboard. dispatchKeyboardEvent(document, 'keydown', TAB); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -537,14 +533,14 @@ describe('cdkMonitorFocus', () => { .withContext('button should have cdk-keyboard-focused class') .toBe(true); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('keyboard'); - }); + })); - it('should detect focus via mouse (directive)', async () => { + it('should detect focus via mouse', fakeAsync(() => { // Simulate focus via mouse. dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -556,14 +552,14 @@ describe('cdkMonitorFocus', () => { .withContext('button should have cdk-mouse-focused class') .toBe(true); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('mouse'); - }); + })); - it('should detect focus via touch (directive)', async () => { + it('should detect focus via touch', fakeAsync(() => { // Simulate focus via touch. dispatchFakeEvent(buttonElement, 'touchstart'); buttonElement.focus(); fixture.detectChanges(); - await wait(TOUCH_BUFFER_MS); + tick(TOUCH_BUFFER_MS); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -575,13 +571,13 @@ describe('cdkMonitorFocus', () => { .withContext('button should have cdk-touch-focused class') .toBe(true); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('touch'); - }); + })); - it('should detect programmatic focus (directive)', async () => { + it('should detect programmatic focus', fakeAsync(() => { // Programmatically focus. buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -593,12 +589,12 @@ describe('cdkMonitorFocus', () => { .withContext('button should have cdk-program-focused class') .toBe(true); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('program'); - }); + })); - it('should remove focus classes on blur (directive)', async () => { + it('should remove focus classes on blur', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(buttonElement.classList.length) .withContext('button should have exactly 2 focus classes') @@ -612,7 +608,7 @@ describe('cdkMonitorFocus', () => { .withContext('button should not have any focus classes') .toBe(0); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith(null); - }); + })); }); describe('complex component with cdkMonitorElementFocus', () => { @@ -631,25 +627,25 @@ describe('cdkMonitorFocus', () => { patchElementFocus(childElement); }); - it('should add focus classes on parent focus', async () => { + it('should add focus classes on parent focus', fakeAsync(() => { parentElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(parentElement.classList.length) .withContext('button should have exactly 2 focus classes') .toBe(2); - }); + })); - it('should not add focus classes on child focus', async () => { + it('should not add focus classes on child focus', fakeAsync(() => { childElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(parentElement.classList.length) .withContext('button should not have any focus classes') .toBe(0); - }); + })); }); describe('complex component with cdkMonitorSubtreeFocus', () => { @@ -668,25 +664,25 @@ describe('cdkMonitorFocus', () => { patchElementFocus(childElement); }); - it('should add focus classes on parent focus', async () => { + it('should add focus classes on parent focus', fakeAsync(() => { parentElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(parentElement.classList.length) .withContext('button should have exactly 2 focus classes') .toBe(2); - }); + })); - it('should add focus classes on child focus', async () => { + it('should add focus classes on child focus', fakeAsync(() => { childElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(parentElement.classList.length) .withContext('button should have exactly 2 focus classes') .toBe(2); - }); + })); }); describe('complex component with cdkMonitorSubtreeFocus and cdkMonitorElementFocus', () => { @@ -709,14 +705,14 @@ describe('cdkMonitorFocus', () => { patchElementFocus(childElement); }); - it('should add keyboard focus classes on both elements when child is focused via keyboard', async () => { + it('should add keyboard focus classes on both elements when child is focused via keyboard', fakeAsync(() => { focusMonitor.focusVia(childElement, 'keyboard'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(parentElement.classList).toContain('cdk-keyboard-focused'); expect(childElement.classList).toContain('cdk-keyboard-focused'); - }); + })); }); describe('button with exported cdkMonitorElementFocus', () => { @@ -731,55 +727,55 @@ describe('cdkMonitorFocus', () => { patchElementFocus(buttonElement); }); - it('should initially not be focused (exported directive)', () => { + it('should initially not be focused', () => { expect(fixture.componentInstance.exportedDirRef.focusOrigin) .withContext('initial focus origin should be null') .toBeNull(); }); - it('should detect focus via keyboard (exported directive)', async () => { + it('should detect focus via keyboard', fakeAsync(() => { // Simulate focus via keyboard. dispatchKeyboardEvent(document, 'keydown', TAB); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual('keyboard'); - }); + })); - it('should detect focus via mouse (exported directive)', async () => { + it('should detect focus via mouse', fakeAsync(() => { // Simulate focus via mouse. dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual('mouse'); - }); + })); - it('should detect focus via touch (exported directive)', async () => { + it('should detect focus via touch', fakeAsync(() => { // Simulate focus via touch. dispatchFakeEvent(buttonElement, 'touchstart'); buttonElement.focus(); fixture.detectChanges(); - await wait(TOUCH_BUFFER_MS); + tick(TOUCH_BUFFER_MS); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual('touch'); - }); + })); - it('should detect programmatic focus (exported directive)', async () => { + it('should detect programmatic focus', fakeAsync(() => { // Programmatically focus. buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual('program'); - }); + })); - it('should remove focus classes on blur (exported directive)', async () => { + it('should remove focus classes on blur', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual('program'); @@ -787,7 +783,7 @@ describe('cdkMonitorFocus', () => { fixture.detectChanges(); expect(fixture.componentInstance.exportedDirRef.focusOrigin).toEqual(null); - }); + })); }); it('should not throw when trying to monitor focus on a non-element node', () => { @@ -817,7 +813,7 @@ describe('FocusMonitor observable stream', () => { patchElementFocus(buttonElement); }); - it('should not emit on the server', async () => { + it('should not emit on the server', fakeAsync(() => { fakePlatform.isBrowser = false; const emitSpy = jasmine.createSpy('emit spy'); const completeSpy = jasmine.createSpy('complete spy'); @@ -828,10 +824,10 @@ describe('FocusMonitor observable stream', () => { buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(emitSpy).not.toHaveBeenCalled(); expect(completeSpy).toHaveBeenCalled(); - }); + })); }); describe('FocusMonitor input label detection', () => { @@ -849,7 +845,7 @@ describe('FocusMonitor input label detection', () => { patchElementFocus(inputElement); }); - it('should detect label click focus as `mouse`', async () => { + it('should detect label click focus as `mouse`', fakeAsync(() => { const spy = jasmine.createSpy('monitor spy'); focusMonitor.monitor(inputElement).subscribe(spy); expect(spy).not.toHaveBeenCalled(); @@ -860,29 +856,27 @@ describe('FocusMonitor input label detection', () => { dispatchMouseEvent(labelElement, 'mousedown'); labelElement.click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); // The programmatic click from above won't move focus so we have to focus the input ourselves. inputElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(inputElement.classList).toContain('cdk-mouse-focused'); expect(spy.calls.mostRecent()?.args[0]).toBe('mouse'); - }); + })); }); @Component({ template: `
    `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PlainButton {} @Component({ template: ``, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ButtonWithFocusClasses { focusChanged(_origin: FocusOrigin) {} @@ -891,28 +885,24 @@ class ButtonWithFocusClasses { @Component({ template: `
    `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComplexComponentWithMonitorElementFocus {} @Component({ template: `
    `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComplexComponentWithMonitorSubtreeFocus {} @Component({ template: `
    `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComplexComponentWithMonitorSubtreeFocusAndMonitorElementFocus {} @Component({ template: ``, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusMonitorOnCommentNode {} @@ -922,14 +912,12 @@ class FocusMonitorOnCommentNode {} `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CheckboxWithLabel {} @Component({ template: ``, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ExportedFocusMonitor { @ViewChild('exportedDir') exportedDirRef!: CdkMonitorFocus; diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.ts b/src/cdk/a11y/focus-monitor/focus-monitor.ts index 3a91121ca191..7d23d712b828 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.ts @@ -16,7 +16,7 @@ import { Directive, ElementRef, EventEmitter, - Service, + Injectable, InjectionToken, NgZone, OnDestroy, @@ -83,7 +83,7 @@ const captureEventListenerOptions = normalizePassiveListenerOptions({ }); /** Monitors mouse and keyboard events to determine the cause of focus events. */ -@Service() +@Injectable({providedIn: 'root'}) export class FocusMonitor implements OnDestroy { private _ngZone = inject(NgZone); private _platform = inject(Platform); @@ -147,6 +147,8 @@ export class FocusMonitor implements OnDestroy { /** Subject for stopping our InputModalityDetector subscription. */ private readonly _stopInputModalityDetector = new Subject(); + constructor(...args: unknown[]); + constructor() { const options = inject(FOCUS_MONITOR_DEFAULT_OPTIONS, { optional: true, @@ -620,6 +622,9 @@ export class CdkMonitorFocus implements AfterViewInit, OnDestroy { @Output() readonly cdkFocusChange = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + get focusOrigin(): FocusOrigin { return this._focusOrigin; } diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.zone.spec.ts b/src/cdk/a11y/focus-monitor/focus-monitor.zone.spec.ts index 3096a26f5916..fda5a31dd5c4 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.zone.spec.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.zone.spec.ts @@ -1,12 +1,7 @@ import {Platform} from '../../platform'; import {patchElementFocus} from '../../testing/private'; -import { - Component, - NgZone, - provideZoneChangeDetection, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, NgZone, provideZoneChangeDetection} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {A11yModule} from '../a11y-module'; import {FocusMonitor} from './focus-monitor'; @@ -28,21 +23,20 @@ describe('FocusMonitor observable stream Zone.js integration', () => { patchElementFocus(buttonElement); }); - it('should emit inside the NgZone', async () => { + it('should emit inside the NgZone', fakeAsync(() => { const spy = jasmine.createSpy('zone spy'); focusMonitor.monitor(buttonElement).subscribe(() => spy(NgZone.isInAngularZone())); expect(spy).not.toHaveBeenCalled(); buttonElement.focus(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(spy).toHaveBeenCalledWith(true); - }); + })); }); @Component({ template: `
    `, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PlainButton {} diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts index b165956ef2ee..c51f8cbaa814 100644 --- a/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap-factory.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, Injector, NgZone, inject, DOCUMENT} from '@angular/core'; +import {Injectable, Injector, NgZone, inject, DOCUMENT} from '@angular/core'; import {InteractivityChecker} from '../interactivity-checker/interactivity-checker'; import {ConfigurableFocusTrap} from './configurable-focus-trap'; import {ConfigurableFocusTrapConfig} from './configurable-focus-trap-config'; @@ -15,7 +15,7 @@ import {FOCUS_TRAP_INERT_STRATEGY, FocusTrapInertStrategy} from './focus-trap-in import {FocusTrapManager} from './focus-trap-manager'; /** Factory that allows easy instantiation of configurable focus traps. */ -@Service() +@Injectable({providedIn: 'root'}) export class ConfigurableFocusTrapFactory { private _checker = inject(InteractivityChecker); private _ngZone = inject(NgZone); @@ -26,6 +26,8 @@ export class ConfigurableFocusTrapFactory { private readonly _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() { const inertStrategy = inject(FOCUS_TRAP_INERT_STRATEGY, {optional: true}); @@ -41,10 +43,22 @@ export class ConfigurableFocusTrapFactory { */ create(element: HTMLElement, config?: ConfigurableFocusTrapConfig): ConfigurableFocusTrap; + /** + * @deprecated Pass a config object instead of the `deferCaptureElements` flag. + * @breaking-change 11.0.0 + */ + create(element: HTMLElement, deferCaptureElements: boolean): ConfigurableFocusTrap; + create( element: HTMLElement, - config: ConfigurableFocusTrapConfig = {defer: false}, + config: ConfigurableFocusTrapConfig | boolean = {defer: false}, ): ConfigurableFocusTrap { + let configObject: ConfigurableFocusTrapConfig; + if (typeof config === 'boolean') { + configObject = {defer: config}; + } else { + configObject = config; + } return new ConfigurableFocusTrap( element, this._checker, @@ -52,7 +66,7 @@ export class ConfigurableFocusTrapFactory { this._document, this._focusTrapManager, this._inertStrategy, - config, + configObject, this._injector, ); } diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts index b68326f7b595..6a6930deb90d 100644 --- a/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap.spec.ts @@ -6,7 +6,6 @@ import { ViewChild, Provider, inject, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import { @@ -114,7 +113,6 @@ function createComponent(
    `, - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleFocusTrap implements AfterViewInit { private _focusTrapFactory = inject(ConfigurableFocusTrapFactory); diff --git a/src/cdk/a11y/focus-trap/configurable-focus-trap.ts b/src/cdk/a11y/focus-trap/configurable-focus-trap.ts index 26b817879e4e..e95a81b6a7dc 100644 --- a/src/cdk/a11y/focus-trap/configurable-focus-trap.ts +++ b/src/cdk/a11y/focus-trap/configurable-focus-trap.ts @@ -41,7 +41,7 @@ export class ConfigurableFocusTrap extends FocusTrap implements ManagedFocusTrap private _focusTrapManager: FocusTrapManager, private _inertStrategy: FocusTrapInertStrategy, config: ConfigurableFocusTrapConfig, - injector: Injector, + injector?: Injector, ) { super(_element, _checker, _ngZone, _document, config.defer, injector); this._focusTrapManager.register(this); diff --git a/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts index 3c4d664ae489..42f8659f25ed 100644 --- a/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts +++ b/src/cdk/a11y/focus-trap/event-listener-inert-strategy.spec.ts @@ -6,9 +6,8 @@ import { Type, ViewChild, inject, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing'; import {patchElementFocus} from '../../testing/private'; import { ConfigurableFocusTrap, @@ -22,48 +21,47 @@ describe('EventListenerFocusTrapInertStrategy', () => { {provide: FOCUS_TRAP_INERT_STRATEGY, useValue: new EventListenerFocusTrapInertStrategy()}, ]; - it('refocuses the first FocusTrap element when focus moves outside the FocusTrap', async () => { + it('refocuses the first FocusTrap element when focus moves outside the FocusTrap', fakeAsync(() => { const fixture = createComponent(SimpleFocusTrap, providers); const componentInstance = fixture.componentInstance; fixture.detectChanges(); - // Focus something outside the FocusTrap. - document.body.focus(); - await new Promise(resolve => setTimeout(resolve, 10)); + componentInstance.outsideFocusableElement.nativeElement.focus(); + flush(); expect(componentInstance.activeElement) .withContext('Expected first focusable element to be focused') .toBe(componentInstance.firstFocusableElement.nativeElement); - }); + })); - it('does not intercept focus when focus moves to another element in the FocusTrap', async () => { + it('does not intercept focus when focus moves to another element in the FocusTrap', fakeAsync(() => { const fixture = createComponent(SimpleFocusTrap, providers); const componentInstance = fixture.componentInstance; fixture.detectChanges(); - await fixture.whenStable(); + flush(); componentInstance.secondFocusableElement.nativeElement.focus(); - await fixture.whenStable(); + flush(); expect(componentInstance.activeElement) .withContext('Expected second focusable element to be focused') .toBe(componentInstance.secondFocusableElement.nativeElement); - }); + })); - it('should not intercept focus if it moved outside the trap and back in again', async () => { + it('should not intercept focus if it moved outside the trap and back in again', fakeAsync(() => { const fixture = createComponent(SimpleFocusTrap, providers); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const {secondFocusableElement, outsideFocusableElement} = fixture.componentInstance; outsideFocusableElement.nativeElement.focus(); secondFocusableElement.nativeElement.focus(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.activeElement) .withContext('Expected second focusable element to be focused') .toBe(secondFocusableElement.nativeElement); - }); + })); }); function createComponent( @@ -83,7 +81,6 @@ function createComponent(
    `, - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleFocusTrap implements AfterViewInit { private _focusTrapFactory = inject(ConfigurableFocusTrapFactory); diff --git a/src/cdk/a11y/focus-trap/focus-trap-manager.ts b/src/cdk/a11y/focus-trap/focus-trap-manager.ts index 916101e61670..61360f6af476 100644 --- a/src/cdk/a11y/focus-trap/focus-trap-manager.ts +++ b/src/cdk/a11y/focus-trap/focus-trap-manager.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; /** * A FocusTrap managed by FocusTrapManager. @@ -19,7 +19,7 @@ export interface ManagedFocusTrap { } /** Injectable that ensures only the most recently enabled FocusTrap is active. */ -@Service() +@Injectable({providedIn: 'root'}) export class FocusTrapManager { // A stack of the FocusTraps on the page. Only the FocusTrap at the // top of the stack is active. diff --git a/src/cdk/a11y/focus-trap/focus-trap.md b/src/cdk/a11y/focus-trap/focus-trap.md index 4379836fbebf..8269c5979d93 100644 --- a/src/cdk/a11y/focus-trap/focus-trap.md +++ b/src/cdk/a11y/focus-trap/focus-trap.md @@ -5,7 +5,7 @@ be used to create accessible experience for components like [modal dialogs](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), where focus must be constrained. -Import `CdkTrapFocus` in your component to use this directive. +This directive is declared in `A11yModule`. #### Example diff --git a/src/cdk/a11y/focus-trap/focus-trap.spec.ts b/src/cdk/a11y/focus-trap/focus-trap.spec.ts index ee0c5b254861..6760eba821a6 100644 --- a/src/cdk/a11y/focus-trap/focus-trap.spec.ts +++ b/src/cdk/a11y/focus-trap/focus-trap.spec.ts @@ -7,7 +7,6 @@ import { ViewContainerRef, ViewEncapsulation, inject, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -324,7 +323,6 @@ function getActiveElement() {
    `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleFocusTrap { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -343,7 +341,6 @@ const AUTO_FOCUS_TEMPLATE = ` @Component({ template: AUTO_FOCUS_TEMPLATE, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapWithAutoCapture { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -355,7 +352,6 @@ class FocusTrapWithAutoCapture { template: AUTO_FOCUS_TEMPLATE, encapsulation: ViewEncapsulation.ShadowDom, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapWithAutoCaptureInShadowDom extends FocusTrapWithAutoCapture {} @@ -369,7 +365,6 @@ class FocusTrapWithAutoCaptureInShadowDom extends FocusTrapWithAutoCapture {} } `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapWithBindings { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -390,7 +385,6 @@ class FocusTrapWithBindings {
    `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapTargets { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -403,7 +397,6 @@ class FocusTrapTargets {
    `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapUnfocusableTarget { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -418,7 +411,6 @@ class FocusTrapUnfocusableTarget {
    `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapWithSvg { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -431,7 +423,6 @@ class FocusTrapWithSvg {
    `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapWithoutFocusableElements { @ViewChild(CdkTrapFocus) focusTrapDirective!: CdkTrapFocus; @@ -450,7 +441,6 @@ class FocusTrapWithoutFocusableElements { `, imports: [A11yModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocusTrapInsidePortal { viewContainerRef = inject(ViewContainerRef); diff --git a/src/cdk/a11y/focus-trap/focus-trap.ts b/src/cdk/a11y/focus-trap/focus-trap.ts index 18567653f3e6..9e9f44ea76b0 100644 --- a/src/cdk/a11y/focus-trap/focus-trap.ts +++ b/src/cdk/a11y/focus-trap/focus-trap.ts @@ -13,7 +13,7 @@ import { Directive, DoCheck, ElementRef, - Service, + Injectable, Injector, Input, NgZone, @@ -64,7 +64,8 @@ export class FocusTrap { readonly _ngZone: NgZone, readonly _document: Document, deferAnchors = false, - readonly _injector: Injector, + /** @breaking-change 20.0.0 param to become required */ + readonly _injector?: Injector, ) { if (!deferAnchors) { this.attachAnchors(); @@ -358,14 +359,19 @@ export class FocusTrap { /** Executes a function when the zone is stable. */ private _executeOnStable(fn: () => any): void { - afterNextRender(fn, {injector: this._injector}); + // TODO: remove this conditional when injector is required in the constructor. + if (this._injector) { + afterNextRender(fn, {injector: this._injector}); + } else { + setTimeout(fn); + } } } /** * Factory that allows easy instantiation of focus traps. */ -@Service() +@Injectable({providedIn: 'root'}) export class FocusTrapFactory { private _checker = inject(InteractivityChecker); private _ngZone = inject(NgZone); @@ -373,6 +379,7 @@ export class FocusTrapFactory { private _document = inject(DOCUMENT); private _injector = inject(Injector); + constructor(...args: unknown[]); constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); } @@ -429,6 +436,8 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC @Input({alias: 'cdkTrapFocusAutoCapture', transform: booleanAttribute}) autoCapture: boolean = false; + constructor(...args: unknown[]); + constructor() { const platform = inject(Platform); @@ -462,7 +471,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC } } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const autoCaptureChange = changes['autoCapture']; if ( diff --git a/src/cdk/a11y/high-contrast-mode/high-contrast-mode-detector.ts b/src/cdk/a11y/high-contrast-mode/high-contrast-mode-detector.ts index 623c312c46e7..6595ee81fd57 100644 --- a/src/cdk/a11y/high-contrast-mode/high-contrast-mode-detector.ts +++ b/src/cdk/a11y/high-contrast-mode/high-contrast-mode-detector.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service, OnDestroy, DOCUMENT} from '@angular/core'; +import {inject, Injectable, OnDestroy, DOCUMENT} from '@angular/core'; import {BreakpointObserver} from '../../layout'; import {Platform} from '../../platform'; @@ -39,7 +39,7 @@ export const HIGH_CONTRAST_MODE_ACTIVE_CSS_CLASS = 'cdk-high-contrast-active'; * Mode. This service does not detect high-contrast mode as added by the Chrome "High Contrast" * browser extension. */ -@Service() +@Injectable({providedIn: 'root'}) export class HighContrastModeDetector implements OnDestroy { private _platform = inject(Platform); @@ -51,6 +51,8 @@ export class HighContrastModeDetector implements OnDestroy { private _document = inject(DOCUMENT); private _breakpointSubscription: Subscription; + constructor(...args: unknown[]); + constructor() { this._breakpointSubscription = inject(BreakpointObserver) .observe('(forced-colors: active)') diff --git a/src/cdk/a11y/id-generator.ts b/src/cdk/a11y/id-generator.ts index 58856ecc22f7..843940cf0b85 100644 --- a/src/cdk/a11y/id-generator.ts +++ b/src/cdk/a11y/id-generator.ts @@ -6,19 +6,17 @@ * found in the LICENSE file at https://angular.dev/license */ -import {APP_ID, inject, Service} from '@angular/core'; +import {APP_ID, inject, Injectable} from '@angular/core'; /** * Keeps track of the ID count per prefix. This helps us make the IDs a bit more deterministic * like they were before the service was introduced. Note that ideally we wouldn't have to do * this, but there are some internal tests that rely on the IDs. - * - * Note: use a map to avoid conflicts with built-in properties. */ -const counters = new Map(); +const counters: Record = {}; /** Service that generates unique IDs for DOM nodes. */ -@Service() +@Injectable({providedIn: 'root'}) export class _IdGenerator { private _appId = inject(APP_ID); private static _infix = `a${Math.floor(Math.random() * 100000).toString()}`; @@ -35,15 +33,10 @@ export class _IdGenerator { prefix += this._appId; } - let count = counters.get(prefix); - - if (count === undefined) { - count = 0; - } else { - count++; + if (!counters.hasOwnProperty(prefix)) { + counters[prefix] = 0; } - counters.set(prefix, count); - return `${prefix}${randomize ? _IdGenerator._infix + '-' : ''}${count}`; + return `${prefix}${randomize ? _IdGenerator._infix + '-' : ''}${counters[prefix]++}`; } } diff --git a/src/cdk/a11y/input-modality/input-modality-detector.spec.ts b/src/cdk/a11y/input-modality/input-modality-detector.spec.ts index fc980918467a..32279e699e47 100644 --- a/src/cdk/a11y/input-modality/input-modality-detector.spec.ts +++ b/src/cdk/a11y/input-modality/input-modality-detector.spec.ts @@ -9,7 +9,7 @@ import { dispatchEvent, createTouchEvent, } from '../../testing/private'; -import {TestBed} from '@angular/core/testing'; +import {fakeAsync, TestBed, tick} from '@angular/core/testing'; import { InputModality, InputModalityDetector, @@ -194,17 +194,17 @@ describe('InputModalityDetector', () => { expect(detector.mostRecentModality).toBe(null); }); - it('should ignore mouse events that occur too closely after a touch event', async () => { + it('should ignore mouse events that occur too closely after a touch event', fakeAsync(() => { setupTest(); dispatchTouchEvent(document, 'touchstart'); dispatchMouseEvent(document, 'mousedown'); expect(detector.mostRecentModality).toBe('touch'); - await new Promise(resolve => setTimeout(resolve, TOUCH_BUFFER_MS)); + tick(TOUCH_BUFFER_MS); dispatchMouseEvent(document, 'mousedown'); expect(detector.mostRecentModality).toBe('mouse'); - }); + })); it('should complete the various observables on destroy', () => { setupTest(); diff --git a/src/cdk/a11y/input-modality/input-modality-detector.ts b/src/cdk/a11y/input-modality/input-modality-detector.ts index 7d3a6434cd79..ccd463f831f6 100644 --- a/src/cdk/a11y/input-modality/input-modality-detector.ts +++ b/src/cdk/a11y/input-modality/input-modality-detector.ts @@ -8,7 +8,7 @@ import {ALT, CONTROL, MAC_META, META, SHIFT} from '../../keycodes'; import { - Service, + Injectable, InjectionToken, OnDestroy, NgZone, @@ -96,7 +96,7 @@ const modalityEventListenerOptions = { * update the input modality to keyboard, but in general this service's behavior is largely * undefined. */ -@Service() +@Injectable({providedIn: 'root'}) export class InputModalityDetector implements OnDestroy { private readonly _platform = inject(Platform); private readonly _listenerCleanups: (() => void)[] | undefined; @@ -183,6 +183,8 @@ export class InputModalityDetector implements OnDestroy { this._mostRecentTarget = _getEventTarget(event); }; + constructor(...args: unknown[]); + constructor() { const ngZone = inject(NgZone); const document = inject(DOCUMENT); diff --git a/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts b/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts index 6e4d348d612a..9b629f59a42c 100644 --- a/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts +++ b/src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts @@ -45,7 +45,7 @@ describe('InteractivityChecker', () => { }); describe('isVisible', () => { - it('should return false for a `display: none` element (isVisible)', () => { + it('should return false for a `display: none` element', () => { testContainerElement.innerHTML = ``; const input = testContainerElement.querySelector('input') as HTMLElement; @@ -54,7 +54,7 @@ describe('InteractivityChecker', () => { .toBe(false); }); - it('should return false for the child of a `display: none` element (isVisible)', () => { + it('should return false for the child of a `display: none` element', () => { testContainerElement.innerHTML = `
    `; @@ -65,7 +65,7 @@ describe('InteractivityChecker', () => { .toBe(false); }); - it('should return false for a `visibility: hidden` element (isVisible)', () => { + it('should return false for a `visibility: hidden` element', () => { testContainerElement.innerHTML = ``; const input = testContainerElement.querySelector('input') as HTMLElement; @@ -74,7 +74,7 @@ describe('InteractivityChecker', () => { .toBe(false); }); - it('should return false for the child of a `visibility: hidden` element (isVisible)', () => { + it('should return false for the child of a `visibility: hidden` element', () => { testContainerElement.innerHTML = `
    `; @@ -158,7 +158,7 @@ describe('InteractivityChecker', () => { }); }); - it('should return false for a `display: none` element (isFocusable)', () => { + it('should return false for a `display: none` element', () => { testContainerElement.innerHTML = ``; const input = testContainerElement.querySelector('input') as HTMLElement; @@ -178,7 +178,7 @@ describe('InteractivityChecker', () => { .toBe(true); }); - it('should return false for the child of a `display: none` element (isFocusable)', () => { + it('should return false for the child of a `display: none` element', () => { testContainerElement.innerHTML = `
    `; @@ -189,7 +189,7 @@ describe('InteractivityChecker', () => { .toBe(false); }); - it('should return false for a `visibility: hidden` element (isFocusable)', () => { + it('should return false for a `visibility: hidden` element', () => { testContainerElement.innerHTML = ``; const input = testContainerElement.querySelector('input') as HTMLElement; @@ -198,7 +198,7 @@ describe('InteractivityChecker', () => { .toBe(false); }); - it('should return false for the child of a `visibility: hidden` element (isFocusable)', () => { + it('should return false for the child of a `visibility: hidden` element', () => { testContainerElement.innerHTML = `
    `; @@ -331,7 +331,7 @@ describe('InteractivityChecker', () => { }); }); - it('should return true for div and span with tabindex == 0 (isFocusable)', () => { + it('should return true for div and span with tabindex == 0', () => { if (shouldSkip) { return; } @@ -365,7 +365,7 @@ describe('InteractivityChecker', () => { }); }); - it('should return true for div and span with tabindex == 0 (isTabbable)', () => { + it('should return true for div and span with tabindex == 0', () => { if (shouldSkip) { return; } diff --git a/src/cdk/a11y/interactivity-checker/interactivity-checker.ts b/src/cdk/a11y/interactivity-checker/interactivity-checker.ts index a43143541310..fb31fe4b989e 100644 --- a/src/cdk/a11y/interactivity-checker/interactivity-checker.ts +++ b/src/cdk/a11y/interactivity-checker/interactivity-checker.ts @@ -7,7 +7,7 @@ */ import {Platform} from '../../platform'; -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; /** * Configuration for the isFocusable method. @@ -27,10 +27,13 @@ export class IsFocusableConfig { * Utility for checking the interactivity of an element, such as whether it is focusable or * tabbable. */ -@Service() +@Injectable({providedIn: 'root'}) export class InteractivityChecker { private _platform = inject(Platform); + constructor(...args: unknown[]); + constructor() {} + /** * Gets whether an element is disabled. * diff --git a/src/cdk/a11y/key-manager/list-key-manager.spec.ts b/src/cdk/a11y/key-manager/list-key-manager.spec.ts index f19907757763..e40f5aae1c12 100644 --- a/src/cdk/a11y/key-manager/list-key-manager.spec.ts +++ b/src/cdk/a11y/key-manager/list-key-manager.spec.ts @@ -1,7 +1,7 @@ import {DOWN_ARROW, END, HOME, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '../../keycodes'; import {createKeyboardEvent} from '../../testing/private'; -import {Component, QueryList, signal, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, QueryList, signal} from '@angular/core'; +import {TestBed, fakeAsync, tick} from '@angular/core/testing'; import {take} from 'rxjs/operators'; import {FocusOrigin} from '../focus-monitor/focus-monitor'; import {ActiveDescendantKeyManager} from './activedescendant-key-manager'; @@ -44,10 +44,6 @@ describe('Key managers', () => { unsupported: KeyboardEvent; }; - function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - beforeEach(() => { itemList = new QueryList(); fakeKeyEvents = { @@ -113,7 +109,7 @@ describe('Key managers', () => { it('should maintain the active item when the signal-based items change', () => { keyManager.destroy(); - @Component({template: '', changeDetection: ChangeDetectionStrategy.Eager}) + @Component({template: ''}) class App {} const fixture = TestBed.createComponent(App); @@ -820,68 +816,68 @@ describe('Key managers', () => { expect(() => invalidManager.withTypeAhead()).toThrowError(/must implement/); }); - it('should debounce the input key presses', async () => { + it('should debounce the input key presses', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" keyManager.onKeydown(createKeyboardEvent('keydown', 78, 'n')); // types "n" keyManager.onKeydown(createKeyboardEvent('keydown', 69, 'e')); // types "e" expect(keyManager.activeItem).not.toBe(itemList.toArray()[0]); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should focus the first item that starts with a letter', async () => { + it('should focus the first item that starts with a letter', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[1]); - }); + })); - it('should not move focus if a modifier, that is not allowed, is pressed', async () => { + it('should not move focus if a modifier, that is not allowed, is pressed', fakeAsync(() => { const tEvent = createKeyboardEvent('keydown', 84, 't', {control: true}); expect(keyManager.activeItem).toBeFalsy(); keyManager.onKeydown(tEvent); // types "t" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBeFalsy(); - }); + })); - it('should always allow the shift key', async () => { + it('should always allow the shift key', fakeAsync(() => { const tEvent = createKeyboardEvent('keydown', 84, 't', {shift: true}); expect(keyManager.activeItem).toBeFalsy(); keyManager.onKeydown(tEvent); // types "t" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBeTruthy(); - }); + })); - it('should focus the first item that starts with sequence of letters', async () => { + it('should focus the first item that starts with sequence of letters', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" keyManager.onKeydown(createKeyboardEvent('keydown', 72, 'h')); // types "h" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[2]); - }); + })); - it('should cancel any pending timers if a navigation key is pressed', async () => { + it('should cancel any pending timers if a navigation key is pressed', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" keyManager.onKeydown(createKeyboardEvent('keydown', 72, 'h')); // types "h" keyManager.onKeydown(fakeKeyEvents.downArrow); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should handle non-English input', async () => { + it('should handle non-English input', fakeAsync(() => { itemList.reset([ new FakeFocusable('едно'), new FakeFocusable('две'), @@ -892,12 +888,12 @@ describe('Key managers', () => { const keyboardEvent = createKeyboardEvent('keydown', 68, 'д'); keyManager.onKeydown(keyboardEvent); // types "д" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[1]); - }); + })); - it('should handle non-letter characters', async () => { + it('should handle non-letter characters', fakeAsync(() => { itemList.reset([ new FakeFocusable('[]'), new FakeFocusable('321'), @@ -906,19 +902,19 @@ describe('Key managers', () => { itemList.notifyOnChanges(); keyManager.onKeydown(createKeyboardEvent('keydown', 192, '`')); // types "`" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[2]); keyManager.onKeydown(createKeyboardEvent('keydown', 51, '3')); // types "3" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[1]); keyManager.onKeydown(createKeyboardEvent('keydown', 219, '[')); // types "[" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should not focus disabled items', async () => { + it('should not focus disabled items', fakeAsync(() => { expect(keyManager.activeItem).toBeFalsy(); const items = itemList.toArray(); @@ -927,12 +923,12 @@ describe('Key managers', () => { itemList.notifyOnChanges(); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBeFalsy(); - }); + })); - it('should start looking for matches after the active item', async () => { + it('should start looking for matches after the active item', fakeAsync(() => { itemList.reset([ new FakeFocusable('Bilbo'), new FakeFocusable('Frodo'), @@ -944,12 +940,12 @@ describe('Key managers', () => { keyManager.setActiveItem(1); keyManager.onKeydown(createKeyboardEvent('keydown', 66, 'b')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[3]); - }); + })); - it('should wrap back around if there were no matches after the active item', async () => { + it('should wrap back around if there were no matches after the active item', fakeAsync(() => { itemList.reset([ new FakeFocusable('Bilbo'), new FakeFocusable('Frodo'), @@ -961,48 +957,48 @@ describe('Key managers', () => { keyManager.setActiveItem(3); keyManager.onKeydown(createKeyboardEvent('keydown', 66, 'b')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should wrap back around if the last item is active', async () => { + it('should wrap back around if the last item is active', fakeAsync(() => { keyManager.setActiveItem(2); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should be able to select the first item', async () => { + it('should be able to select the first item', fakeAsync(() => { keyManager.setActiveItem(-1); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[0]); - }); + })); - it('should not do anything if there is no match', async () => { + it('should not do anything if there is no match', fakeAsync(() => { keyManager.setActiveItem(1); keyManager.onKeydown(createKeyboardEvent('keydown', 87, 'w')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBe(itemList.toArray()[1]); - }); + })); - it('should expose whether the user is currently typing', async () => { + it('should expose whether the user is currently typing', fakeAsync(() => { expect(keyManager.isTyping()).toBe(false); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" expect(keyManager.isTyping()).toBe(true); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.isTyping()).toBe(false); - }); + })); - it('should reset isTyping if the key manager is destroyed', async () => { + it('should reset isTyping if the key manager is destroyed', fakeAsync(() => { expect(keyManager.isTyping()).toBe(false); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" @@ -1010,18 +1006,18 @@ describe('Key managers', () => { keyManager.destroy(); expect(keyManager.isTyping()).toBe(false); - }); + })); - it('should be able to cancel the typeahead sequence', async () => { + it('should be able to cancel the typeahead sequence', fakeAsync(() => { expect(keyManager.activeItem).toBeFalsy(); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" expect(keyManager.activeItem).toBeFalsy(); keyManager.cancelTypeahead(); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.activeItem).toBeFalsy(); - }); + })); }); }); diff --git a/src/cdk/a11y/key-manager/tree-key-manager.spec.ts b/src/cdk/a11y/key-manager/tree-key-manager.spec.ts index 87155b6c373a..b5c2d83bc87e 100644 --- a/src/cdk/a11y/key-manager/tree-key-manager.spec.ts +++ b/src/cdk/a11y/key-manager/tree-key-manager.spec.ts @@ -3,6 +3,7 @@ import {QueryList} from '@angular/core'; import {TreeKeyManager} from './tree-key-manager'; import {TreeKeyManagerItem} from './tree-key-manager-strategy'; import {Observable, of as observableOf, Subscription} from 'rxjs'; +import {fakeAsync, tick} from '@angular/core/testing'; class FakeBaseTreeKeyManagerItem implements TreeKeyManagerItem { _isExpanded = false; @@ -99,10 +100,6 @@ describe('TreeKeyManager', () => { }; }); - function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - const itemParameters: ItemConstructorTestContext[] = [ {description: 'Observable children', constructor: FakeObservableTreeKeyManagerItem}, {description: 'array children', constructor: FakeArrayTreeKeyManagerItem}, @@ -697,52 +694,87 @@ describe('TreeKeyManager', () => { ).toThrowError(/must implement/); }); - it('should debounce the input key presses', async () => { + it('should debounce the input key presses', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" - await wait(50); + tick(1); keyManager.onKeydown(createKeyboardEvent('keydown', 78, 'n')); // types "n" - await wait(50); + tick(1); keyManager.onKeydown(createKeyboardEvent('keydown', 69, 'e')); // types "e" expect(keyManager.getActiveItemIndex()) .withContext('active item index, before debounce interval') .not.toBe(0); - await wait(debounceInterval * 1.5); + tick(debounceInterval - 1); + + expect(keyManager.getActiveItemIndex()) + .withContext('active item index, after partial debounce interval') + .not.toBe(0); + + tick(1); expect(keyManager.getActiveItemIndex()) .withContext('active item index, after full debounce interval') .toBe(0); - }); + })); + + it('uses a default debounce interval', fakeAsync(() => { + const defaultInterval = 200; + keyManager = new TreeKeyManager(itemList, { + typeAheadDebounceInterval: true, + }); + + keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" + tick(1); + keyManager.onKeydown(createKeyboardEvent('keydown', 78, 'n')); // types "n" + tick(1); + keyManager.onKeydown(createKeyboardEvent('keydown', 69, 'e')); // types "e" + + expect(keyManager.getActiveItemIndex()) + .withContext('active item index, before debounce interval') + .not.toBe(0); + + tick(defaultInterval - 1); - it('should focus the first item that starts with a letter', async () => { + expect(keyManager.getActiveItemIndex()) + .withContext('active item index, after partial debounce interval') + .not.toBe(0); + + tick(1); + + expect(keyManager.getActiveItemIndex()) + .withContext('active item index, after full debounce interval') + .toBe(0); + })); + + it('should focus the first item that starts with a letter', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(1); - }); + })); - it('should focus the first item that starts with sequence of letters', async () => { + it('should focus the first item that starts with sequence of letters', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" keyManager.onKeydown(createKeyboardEvent('keydown', 72, 'h')); // types "h" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(2); - }); + })); - it('should cancel any pending timers if a navigation key is pressed', async () => { + it('should cancel any pending timers if a navigation key is pressed', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 84, 't')); // types "t" keyManager.onKeydown(createKeyboardEvent('keydown', 72, 'h')); // types "h" keyManager.onKeydown(fakeKeyEvents.downArrow); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(0); - }); + })); - it('should handle non-English input', async () => { + it('should handle non-English input', fakeAsync(() => { itemList.reset([ new itemParam.constructor('едно'), new itemParam.constructor('две'), @@ -753,12 +785,12 @@ describe('TreeKeyManager', () => { const keyboardEvent = createKeyboardEvent('keydown', 68, 'д'); keyManager.onKeydown(keyboardEvent); // types "д" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(1); - }); + })); - it('should handle non-letter characters', async () => { + it('should handle non-letter characters', fakeAsync(() => { itemList.reset([ new itemParam.constructor('[]'), new itemParam.constructor('321'), @@ -767,30 +799,30 @@ describe('TreeKeyManager', () => { itemList.notifyOnChanges(); keyManager.onKeydown(createKeyboardEvent('keydown', 192, '`')); // types "`" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(2); keyManager.onKeydown(createKeyboardEvent('keydown', 51, '3')); // types "3" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(1); keyManager.onKeydown(createKeyboardEvent('keydown', 219, '[')); // types "[" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(0); - }); + })); - it('should allow focus to disabled items', async () => { + it('should allow focus to disabled items', fakeAsync(() => { expect(keyManager.getActiveItemIndex()).withContext('initial active item index').toBe(-1); parentItem.isDisabled = true; keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); // types "o" - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('initial active item index').toBe(0); - }); + })); - it('should start looking for matches after the active item', async () => { + it('should start looking for matches after the active item', fakeAsync(() => { const frodo = new itemParam.constructor('Frodo'); itemList.reset([ new itemParam.constructor('Bilbo'), @@ -803,12 +835,12 @@ describe('TreeKeyManager', () => { keyManager.focusItem(frodo); keyManager.onKeydown(createKeyboardEvent('keydown', 66, 'b')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(3); - }); + })); - it('should wrap back around if there were no matches after the active item', async () => { + it('should wrap back around if there were no matches after the active item', fakeAsync(() => { const boromir = new itemParam.constructor('Boromir'); itemList.reset([ new itemParam.constructor('Bilbo'), @@ -821,32 +853,32 @@ describe('TreeKeyManager', () => { keyManager.focusItem(boromir); keyManager.onKeydown(createKeyboardEvent('keydown', 66, 'b')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(0); - }); + })); - it('should wrap back around if the last item is active', async () => { + it('should wrap back around if the last item is active', fakeAsync(() => { keyManager.focusItem(lastItem); keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(0); - }); + })); - it('should be able to select the first item', async () => { + it('should be able to select the first item', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 79, 'o')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(0); - }); + })); - it('should not do anything if there is no match', async () => { + it('should not do anything if there is no match', fakeAsync(() => { keyManager.onKeydown(createKeyboardEvent('keydown', 87, 'w')); - await wait(debounceInterval + 100); + tick(debounceInterval); expect(keyManager.getActiveItemIndex()).withContext('active item index').toBe(-1); - }); + })); }); describe('focusItem', () => { diff --git a/src/cdk/a11y/live-announcer/live-announcer.md b/src/cdk/a11y/live-announcer/live-announcer.md index 2068bb56c503..c58adaa6f9f1 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.md +++ b/src/cdk/a11y/live-announcer/live-announcer.md @@ -10,16 +10,16 @@ Announce the given message via aria-live region. The politeness argument determi `aria-live` attribute on the announcer element, defaulting to 'polite'. #### Examples -The `LiveAnnouncer` is injected into a component: +The LiveAnnouncer is injected into a component: ```ts @Component({ - selector: 'my-component', + selector: 'my-component' + providers: [LiveAnnouncer] }) export class MyComponent { - private liveAnnouncer = inject(LiveAnnouncer); - announceMessage() { - this.liveAnnouncer.announce("Hey Google"); + constructor(liveAnnouncer: LiveAnnouncer) { + liveAnnouncer.announce("Hey Google"); } } ``` diff --git a/src/cdk/a11y/live-announcer/live-announcer.spec.ts b/src/cdk/a11y/live-announcer/live-announcer.spec.ts index 1f407755b5b1..0ce4d0cf391a 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.spec.ts +++ b/src/cdk/a11y/live-announcer/live-announcer.spec.ts @@ -1,7 +1,7 @@ import {MutationObserverFactory} from '../../observers'; import {ComponentPortal} from '../../portal'; -import {Component, inject, Injector, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, inject, Injector} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {By, DomSanitizer} from '@angular/platform-browser'; import {A11yModule} from '../index'; import {LiveAnnouncer, LiveAnnouncerMessage} from './live-announcer'; @@ -18,80 +18,76 @@ describe('LiveAnnouncer', () => { let ariaLiveElement: Element; let fixture: ComponentFixture; - function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - describe('with default element', () => { - beforeEach(async () => { + beforeEach(fakeAsync(() => { announcer = TestBed.inject(LiveAnnouncer); ariaLiveElement = getLiveElement(); fixture = TestBed.createComponent(TestApp); - }); + })); - it('should correctly update the announce text', async () => { + it('should correctly update the announce text', fakeAsync(() => { let buttonElement = fixture.debugElement.query(By.css('button'))!.nativeElement; buttonElement.click(); // This flushes our 100ms timeout for the screenreaders. - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Test'); - }); + })); - it('should correctly update the politeness attribute', async () => { + it('should correctly update the politeness attribute', fakeAsync(() => { announcer.announce('Hey Google', 'assertive'); // This flushes our 100ms timeout for the screenreaders. - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Hey Google'); expect(ariaLiveElement.getAttribute('aria-live')).toBe('assertive'); - }); + })); - it('should apply the aria-live value polite by default', async () => { + it('should apply the aria-live value polite by default', fakeAsync(() => { announcer.announce('Hey Google'); // This flushes our 100ms timeout for the screenreaders. - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Hey Google'); expect(ariaLiveElement.getAttribute('aria-live')).toBe('polite'); - }); + })); - it('should be able to clear out the aria-live element manually', async () => { + it('should be able to clear out the aria-live element manually', fakeAsync(() => { announcer.announce('Hey Google'); - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Hey Google'); announcer.clear(); expect(ariaLiveElement.textContent).toBeFalsy(); - }); + })); - it('should be able to clear out the aria-live element by setting a duration', async () => { + it('should be able to clear out the aria-live element by setting a duration', fakeAsync(() => { announcer.announce('Hey Google', 2000); - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Hey Google'); - await wait(2010); + tick(2000); expect(ariaLiveElement.textContent).toBeFalsy(); - }); + })); - it('should clear the duration of previous messages when announcing a new one', async () => { + it('should clear the duration of previous messages when announcing a new one', fakeAsync(() => { announcer.announce('Hey Google', 2000); - await wait(100); + tick(100); expect(ariaLiveElement.textContent).toBe('Hey Google'); announcer.announce('Hello there'); - await wait(2500); + tick(2500); expect(ariaLiveElement.textContent).toBe('Hello there'); - }); + })); - it('should remove the aria-live element from the DOM on destroy', async () => { + it('should remove the aria-live element from the DOM on destroy', fakeAsync(() => { announcer.announce('Hey Google'); // This flushes our 100ms timeout for the screenreaders. - await wait(100); + tick(100); // Call the lifecycle hook manually since Angular won't do it in tests. announcer.ngOnDestroy(); @@ -99,28 +95,28 @@ describe('LiveAnnouncer', () => { expect(document.body.querySelector('.cdk-live-announcer-element')) .withContext('Expected that the aria-live element was remove from the DOM.') .toBeFalsy(); - }); + })); - it('should return a promise that resolves after the text has been announced', async () => { + it('should return a promise that resolves after the text has been announced', fakeAsync(() => { const spy = jasmine.createSpy('announce spy'); announcer.announce('something').then(spy); expect(spy).not.toHaveBeenCalled(); - await wait(110); + tick(100); expect(spy).toHaveBeenCalled(); - }); + })); - it('should resolve the returned promise if another announcement is made before the timeout has expired', async () => { + it('should resolve the returned promise if another announcement is made before the timeout has expired', fakeAsync(() => { const spy = jasmine.createSpy('announce spy'); announcer.announce('something').then(spy); - await wait(10); + tick(10); announcer.announce('something').then(spy); - await wait(110); + tick(100); expect(spy).toHaveBeenCalledTimes(2); - }); + })); - it('should ensure that there is only one live element at a time', async () => { + it('should ensure that there is only one live element at a time', fakeAsync(() => { fixture.destroy(); TestBed.resetTestingModule().configureTestingModule({}); @@ -132,39 +128,39 @@ describe('LiveAnnouncer', () => { fixture = TestBed.createComponent(TestApp); announcer.announce('Hey Google'); - await wait(110); + tick(100); expect(document.body.querySelectorAll('.cdk-live-announcer-element').length) .withContext('Expected only one live announcer element in the DOM.') .toBe(1); extraElement.remove(); - }); + })); - it('should clear any previous timers when a new one is started', async () => { + it('should clear any previous timers when a new one is started', fakeAsync(() => { expect(ariaLiveElement.textContent).toBeFalsy(); announcer.announce('One'); - await wait(50); + tick(50); announcer.announce('Two'); - await wait(75); + tick(75); expect(ariaLiveElement.textContent).toBeFalsy(); - await wait(100); + tick(25); expect(ariaLiveElement.textContent).toBe('Two'); - }); + })); - it('should clear pending timeouts on destroy', async () => { + it('should clear pending timeouts on destroy', fakeAsync(() => { announcer.announce('Hey Google'); announcer.ngOnDestroy(); // Since we're testing whether the timeouts were flushed, we don't need any // assertions here. `fakeAsync` will fail the test if a timer was left over. - }); + })); - it('should add aria-owns to open aria-modal elements', async () => { + it('should add aria-owns to open aria-modal elements', fakeAsync(() => { const portal = new ComponentPortal(TestModal); const overlayRef = createOverlayRef(TestBed.inject(Injector)); const componentRef = overlayRef.attach(portal); @@ -175,16 +171,16 @@ describe('LiveAnnouncer', () => { expect(modal.hasAttribute('aria-owns')).toBe(false); announcer.announce('Hey Google', 'assertive'); - await wait(110); + tick(100); expect(modal.getAttribute('aria-owns')).toBe(ariaLiveElement.id); // Verify that the ID isn't duplicated. announcer.announce('Hey Google again', 'assertive'); - await wait(110); + tick(100); expect(modal.getAttribute('aria-owns')).toBe(ariaLiveElement.id); - }); + })); - it('should expand aria-owns of open aria-modal elements', async () => { + it('should expand aria-owns of open aria-modal elements', fakeAsync(() => { const portal = new ComponentPortal(TestModal); const overlayRef = createOverlayRef(TestBed.inject(Injector)); const componentRef = overlayRef.attach(portal); @@ -198,16 +194,16 @@ describe('LiveAnnouncer', () => { expect(modal.getAttribute('aria-owns')).toBe('foo bar'); announcer.announce('Hey Google', 'assertive'); - await wait(110); + tick(100); expect(modal.getAttribute('aria-owns')).toBe(`foo bar ${ariaLiveElement.id}`); // Verify that the ID isn't duplicated. announcer.announce('Hey Google again', 'assertive'); - await wait(110); + tick(100); expect(modal.getAttribute('aria-owns')).toBe(`foo bar ${ariaLiveElement.id}`); - }); + })); - it('should be able to announce safe HTML', async () => { + it('should be able to announce safe HTML', fakeAsync(() => { const sanitizer = TestBed.inject(DomSanitizer); const message = sanitizer.bypassSecurityTrustHtml( 'Bonjour', @@ -215,10 +211,10 @@ describe('LiveAnnouncer', () => { fixture.componentInstance.announce(message); // This flushes our 100ms timeout for the screenreaders. - await wait(110); + tick(100); expect(ariaLiveElement.querySelector('.message')?.textContent).toBe('Bonjour'); - }); + })); }); describe('with a custom element', () => { @@ -237,14 +233,14 @@ describe('LiveAnnouncer', () => { ariaLiveElement = getLiveElement(); }); - it('should allow to use a custom live element', async () => { + it('should allow to use a custom live element', fakeAsync(() => { announcer.announce('Custom Element'); // This flushes our 100ms timeout for the screenreaders. - await wait(110); + tick(100); expect(customLiveElement.textContent).toBe('Custom Element'); - }); + })); }); describe('with a default options', () => { @@ -267,23 +263,23 @@ describe('LiveAnnouncer', () => { ariaLiveElement = getLiveElement(); }); - it('should pick up the default politeness from the injection token', async () => { + it('should pick up the default politeness from the injection token', fakeAsync(() => { announcer.announce('Hello'); - await wait(2000); + tick(2000); expect(ariaLiveElement.getAttribute('aria-live')).toBe('assertive'); - }); + })); - it('should pick up the default duration from the injection token', async () => { + it('should pick up the default politeness from the injection token', fakeAsync(() => { announcer.announce('Hello'); - await wait(110); + tick(100); expect(ariaLiveElement.textContent).toBe('Hello'); - await wait(1500); + tick(1337); expect(ariaLiveElement.textContent).toBeFalsy(); - }); + })); }); }); @@ -295,7 +291,7 @@ describe('CdkAriaLive', () => { const invokeMutationCallbacks = () => mutationCallbacks.forEach(cb => cb([{type: 'fake'}])); - beforeEach(async () => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ { @@ -312,32 +308,32 @@ describe('CdkAriaLive', () => { }, ], }); - }); + })); - beforeEach(async () => { + beforeEach(fakeAsync(() => { announcer = TestBed.inject(LiveAnnouncer); announcerSpy = spyOn(announcer, 'announce').and.callThrough(); fixture = TestBed.createComponent(DivWithCdkAriaLive); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should default politeness to polite', async () => { + it('should default politeness to polite', fakeAsync(() => { fixture.componentInstance.content = 'New content'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledWith('New content', 'polite', undefined); - }); + })); - it('should dynamically update the politeness', async () => { + it('should dynamically update the politeness', fakeAsync(() => { fixture.componentInstance.content = 'New content'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledWith('New content', 'polite', undefined); @@ -347,7 +343,7 @@ describe('CdkAriaLive', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).not.toHaveBeenCalled(); @@ -357,37 +353,37 @@ describe('CdkAriaLive', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledWith('Newest content', 'assertive', undefined); - }); + })); - it('should not announce the same text multiple times', async () => { + it('should not announce the same text multiple times', fakeAsync(() => { fixture.componentInstance.content = 'Content'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledTimes(1); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledTimes(1); - }); + })); - it('should be able to pass in a duration', async () => { + it('should be able to pass in a duration', fakeAsync(() => { fixture.componentInstance.content = 'New content'; fixture.componentInstance.duration = 1337; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); invokeMutationCallbacks(); - await fixture.whenStable(); + flush(); expect(announcer.announce).toHaveBeenCalledWith('New content', 'polite', 1337); - }); + })); }); function getLiveElement(): Element { @@ -397,7 +393,6 @@ function getLiveElement(): Element { @Component({ template: ``, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { live = inject(LiveAnnouncer); @@ -411,7 +406,6 @@ class TestApp { template: '', host: {'[attr.aria-owns]': 'ariaOwns', 'aria-modal': 'true'}, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestModal { ariaOwns: string | null = null; @@ -423,7 +417,6 @@ class TestModal { [cdkAriaLive]="politeness" [cdkAriaLiveDuration]="duration">{{content}}`, imports: [A11yModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DivWithCdkAriaLive { politeness: AriaLivePoliteness = 'polite'; diff --git a/src/cdk/a11y/live-announcer/live-announcer.ts b/src/cdk/a11y/live-announcer/live-announcer.ts index 55437de36ebc..355e99c9b33f 100644 --- a/src/cdk/a11y/live-announcer/live-announcer.ts +++ b/src/cdk/a11y/live-announcer/live-announcer.ts @@ -11,7 +11,7 @@ import {ContentObserver} from '../../observers'; import { Directive, ElementRef, - Service, + Injectable, Input, NgZone, OnDestroy, @@ -33,7 +33,7 @@ let uniqueIds = 0; /** Possible types for a message that can be announced by the `LiveAnnouncer`. */ export type LiveAnnouncerMessage = string | SafeHtml; -@Service() +@Injectable({providedIn: 'root'}) export class LiveAnnouncer implements OnDestroy { private _ngZone = inject(NgZone); private _defaultOptions = inject(LIVE_ANNOUNCER_DEFAULT_OPTIONS, { @@ -47,6 +47,8 @@ export class LiveAnnouncer implements OnDestroy { private _currentPromise: Promise | undefined; private _currentResolve: (() => void) | undefined; + constructor(...args: unknown[]); + constructor() { const elementToken = inject(LIVE_ANNOUNCER_ELEMENT_TOKEN, {optional: true}); this._liveElement = elementToken || this._createLiveElement(); @@ -274,6 +276,8 @@ export class CdkAriaLive implements OnDestroy { private _previousAnnouncedText?: string; private _subscription: Subscription | undefined; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); } diff --git a/src/cdk/a11y/public-api.ts b/src/cdk/a11y/public-api.ts index 9b0534690742..df192ec2c339 100644 --- a/src/cdk/a11y/public-api.ts +++ b/src/cdk/a11y/public-api.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -export {AriaDescriber, RegisteredMessage} from './aria-describer/aria-describer'; +export * from './aria-describer/aria-describer'; export * from './aria-describer/aria-reference'; export * from './key-manager/activedescendant-key-manager'; export * from './key-manager/focus-key-manager'; diff --git a/src/cdk/accordion/accordion-item.spec.ts b/src/cdk/accordion/accordion-item.spec.ts index cec2947451fc..688e54dbb828 100644 --- a/src/cdk/accordion/accordion-item.spec.ts +++ b/src/cdk/accordion/accordion-item.spec.ts @@ -1,5 +1,5 @@ import {TestBed, ComponentFixture} from '@angular/core/testing'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; import {CdkAccordionModule, CdkAccordionItem} from './public-api'; @@ -226,7 +226,6 @@ describe('CdkAccordionItem', () => { @Component({ template: ``, imports: [CdkAccordionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SingleItem {} @@ -236,7 +235,6 @@ class SingleItem {} `, imports: [CdkAccordionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ItemGroupWithoutAccordion {} @@ -248,6 +246,5 @@ class ItemGroupWithoutAccordion {} `, imports: [CdkAccordionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ItemGroupWithAccordion {} diff --git a/src/cdk/accordion/accordion-item.ts b/src/cdk/accordion/accordion-item.ts index 6a11fd652dcf..bd32f3f10d8c 100644 --- a/src/cdk/accordion/accordion-item.ts +++ b/src/cdk/accordion/accordion-item.ts @@ -103,6 +103,9 @@ export class CdkAccordionItem implements OnInit, OnDestroy { /** Unregister function for _expansionDispatcher. */ private _removeUniqueSelectionListener: () => void = () => {}; + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { this._removeUniqueSelectionListener = this._expansionDispatcher.listen( (id: string, accordionId: string) => { diff --git a/src/cdk/accordion/accordion.spec.ts b/src/cdk/accordion/accordion.spec.ts index a9669ff02299..fe0381eca803 100644 --- a/src/cdk/accordion/accordion.spec.ts +++ b/src/cdk/accordion/accordion.spec.ts @@ -1,11 +1,5 @@ import {TestBed} from '@angular/core/testing'; -import { - Component, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {By} from '@angular/platform-browser'; import {CdkAccordion} from './accordion'; import {CdkAccordionItem} from './accordion-item'; @@ -115,7 +109,6 @@ describe('CdkAccordion', () => { `, imports: [CdkAccordionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SetOfItems { @ViewChild(CdkAccordion) accordion!: CdkAccordion; @@ -131,7 +124,6 @@ class SetOfItems { `, imports: [CdkAccordionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedItems { @ViewChild('outerItem') outerItem!: CdkAccordionItem; diff --git a/src/cdk/accordion/accordion.ts b/src/cdk/accordion/accordion.ts index e9d88c783ca7..9a5f6200ea3f 100644 --- a/src/cdk/accordion/accordion.ts +++ b/src/cdk/accordion/accordion.ts @@ -36,7 +36,7 @@ export const CDK_ACCORDION = new InjectionToken('CdkAccordion'); }) export class CdkAccordion implements OnDestroy, OnChanges { /** Emits when the state of the accordion changes */ - readonly _stateChanges = new Subject>(); + readonly _stateChanges = new Subject(); /** Stream that emits true/false when openAll/closeAll is triggered. */ readonly _openCloseAllActions: Subject = new Subject(); @@ -59,7 +59,7 @@ export class CdkAccordion implements OnDestroy, OnChanges { this._openCloseAllActions.next(false); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { this._stateChanges.next(changes); } diff --git a/src/cdk/bidi/directionality.spec.ts b/src/cdk/bidi/directionality.spec.ts index 71167ea4b9c8..f21993819f25 100644 --- a/src/cdk/bidi/directionality.spec.ts +++ b/src/cdk/bidi/directionality.spec.ts @@ -1,5 +1,5 @@ import {waitForAsync, fakeAsync, TestBed, flush} from '@angular/core/testing'; -import {Component, ViewChild, signal, inject, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild, signal, inject} from '@angular/core'; import {By} from '@angular/platform-browser'; import {BidiModule, Directionality, Dir, Direction, DIR_DOCUMENT} from './index'; @@ -156,7 +156,6 @@ describe('Directionality', () => { selector: 'injects-directionality', template: `
    `, imports: [BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InjectsDirectionality { dir = inject(Directionality); @@ -169,7 +168,6 @@ class InjectsDirectionality { `, imports: [Dir, InjectsDirectionality], - changeDetection: ChangeDetectionStrategy.Eager, }) class ElementWithDir { @ViewChild(Dir) dir!: Dir; @@ -180,7 +178,6 @@ class ElementWithDir { @Component({ template: '
    ', imports: [Dir], - changeDetection: ChangeDetectionStrategy.Eager, }) class ElementWithPredefinedAutoDir { @ViewChild(Dir) dir!: Dir; @@ -189,7 +186,6 @@ class ElementWithPredefinedAutoDir { @Component({ template: '
    ', imports: [Dir], - changeDetection: ChangeDetectionStrategy.Eager, }) class ElementWithPredefinedUppercaseDir { @ViewChild(Dir) dir!: Dir; diff --git a/src/cdk/bidi/directionality.ts b/src/cdk/bidi/directionality.ts index 372ee976a85b..4c74b0734320 100644 --- a/src/cdk/bidi/directionality.ts +++ b/src/cdk/bidi/directionality.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {EventEmitter, Service, OnDestroy, inject, signal} from '@angular/core'; +import {EventEmitter, Injectable, OnDestroy, inject, signal} from '@angular/core'; import {DIR_DOCUMENT} from './dir-document-token'; export type Direction = 'ltr' | 'rtl'; @@ -30,7 +30,7 @@ export function _resolveDirectionality(rawValue: string): Direction { * The directionality (LTR / RTL) context for the application (or a subtree of it). * Exposes the current direction and a stream of direction changes. */ -@Service() +@Injectable({providedIn: 'root'}) export class Directionality implements OnDestroy { /** The current 'ltr' or 'rtl' value. */ get value() { @@ -45,6 +45,8 @@ export class Directionality implements OnDestroy { /** Stream that emits whenever the 'ltr' / 'rtl' state changes. */ readonly change = new EventEmitter(); + constructor(...args: unknown[]); + constructor() { const _document = inject(DIR_DOCUMENT, {optional: true}); diff --git a/src/cdk/clipboard/clipboard.md b/src/cdk/clipboard/clipboard.md index 82ac543ae136..d1217a0349b7 100644 --- a/src/cdk/clipboard/clipboard.md +++ b/src/cdk/clipboard/clipboard.md @@ -21,7 +21,7 @@ directly to place it on the clipboard. import {Clipboard} from '@angular/cdk/clipboard'; class HeroProfile { - private clipboard = inject(Clipboard); + constructor(private clipboard: Clipboard) {} copyHeroName() { this.clipboard.copy('Alphonso'); @@ -39,10 +39,10 @@ the text that was buffered. Please note, if you call `beginCopy`, you must clean import {Clipboard} from '@angular/cdk/clipboard'; class HeroProfile { - private clipboard = inject(Clipboard); - lifetimeAchievements: string; + constructor(private clipboard: Clipboard) {} + copyAchievements() { const pending = this.clipboard.beginCopy(this.lifetimeAchievements); let remainingAttempts = 3; diff --git a/src/cdk/clipboard/clipboard.ts b/src/cdk/clipboard/clipboard.ts index 57ada1bcdcc4..c8e8f279ff4f 100644 --- a/src/cdk/clipboard/clipboard.ts +++ b/src/cdk/clipboard/clipboard.ts @@ -6,16 +6,19 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, inject, DOCUMENT} from '@angular/core'; +import {Injectable, inject, DOCUMENT} from '@angular/core'; import {PendingCopy} from './pending-copy'; /** * A service for copying text to the clipboard. */ -@Service() +@Injectable({providedIn: 'root'}) export class Clipboard { private readonly _document = inject(DOCUMENT); + constructor(...args: unknown[]); + constructor() {} + /** * Copies the provided text into the user's clipboard. * diff --git a/src/cdk/clipboard/copy-to-clipboard.spec.ts b/src/cdk/clipboard/copy-to-clipboard.spec.ts index 071cb3e2c156..56724a76df1b 100644 --- a/src/cdk/clipboard/copy-to-clipboard.spec.ts +++ b/src/cdk/clipboard/copy-to-clipboard.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {Clipboard} from './clipboard'; @@ -15,7 +15,6 @@ const COPY_CONTENT = 'copy content'; [cdkCopyToClipboardAttempts]="attempts" (cdkCopyToClipboardCopied)="copied($event)">`, imports: [ClipboardModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CopyToClipboardHost { content = ''; diff --git a/src/cdk/clipboard/copy-to-clipboard.ts b/src/cdk/clipboard/copy-to-clipboard.ts index ac187154a6ba..d0fb44cc2421 100644 --- a/src/cdk/clipboard/copy-to-clipboard.ts +++ b/src/cdk/clipboard/copy-to-clipboard.ts @@ -49,8 +49,7 @@ export class CdkCopyToClipboard implements OnDestroy { /** * How many times to attempt to copy the text. This may be necessary for longer text, because - * the browser needs time to fill an intermediate textarea element and copy the content. Attempts - * are cappted out at 50. + * the browser needs time to fill an intermediate textarea element and copy the content. */ @Input('cdkCopyToClipboardAttempts') attempts: number = 1; @@ -69,6 +68,8 @@ export class CdkCopyToClipboard implements OnDestroy { /** Timeout for the current copy attempt. */ private _currentTimeout: any; + constructor(...args: unknown[]); + constructor() { const config = inject(CDK_COPY_TO_CLIPBOARD_CONFIG, {optional: true}); @@ -79,9 +80,6 @@ export class CdkCopyToClipboard implements OnDestroy { /** Copies the current text to the clipboard. */ copy(attempts: number = this.attempts): void { - // Avoid triggering an infinite loop if a large number of attempts is passed in. - attempts = Math.min(attempts, 50); - if (attempts > 1) { let remainingAttempts = attempts; const pending = this._clipboard.beginCopy(this.text); diff --git a/src/cdk/collections/selection-model.ts b/src/cdk/collections/selection-model.ts index ed47a27cae6e..0b6c0eb0f36f 100644 --- a/src/cdk/collections/selection-model.ts +++ b/src/cdk/collections/selection-model.ts @@ -36,21 +36,6 @@ export class SelectionModel { /** Event emitted when the value has changed. */ readonly changed = new Subject>(); - /** - * Exposes selection/deselection methods that work on array of values and don't expect a spread. - * This is useful in the cases where you may have a large collection of items that can't be - * easily spread into the existing methods without hitting browser limits. - */ - readonly bulk: Readonly<{ - select: (values: T[]) => boolean; - deselect: (values: T[]) => boolean; - setSelection: (values: T[]) => boolean; - }> = { - select: values => this._select(values), - deselect: values => this._deselect(values), - setSelection: values => this._setSelection(values), - }; - constructor( private _multiple = false, initiallySelectedValues?: T[], @@ -75,7 +60,11 @@ export class SelectionModel { * @return Whether the selection changed as a result of this call */ select(...values: T[]): boolean { - return this._select(values); + this._verifyValueAssignment(values); + values.forEach(value => this._markSelected(value)); + const changed = this._hasQueuedChanges(); + this._emitChangeEvent(); + return changed; } /** @@ -84,7 +73,11 @@ export class SelectionModel { * @return Whether the selection changed as a result of this call */ deselect(...values: T[]): boolean { - return this._deselect(values); + this._verifyValueAssignment(values); + values.forEach(value => this._unmarkSelected(value)); + const changed = this._hasQueuedChanges(); + this._emitChangeEvent(); + return changed; } /** @@ -93,7 +86,16 @@ export class SelectionModel { * @return Whether the selection changed as a result of this call */ setSelection(...values: T[]): boolean { - return this._setSelection(values); + this._verifyValueAssignment(values); + const oldValues = this.selected; + const newSelectedSet = new Set(values.map(value => this._getConcreteValue(value))); + values.forEach(value => this._markSelected(value)); + oldValues + .filter(value => !newSelectedSet.has(this._getConcreteValue(value, newSelectedSet))) + .forEach(value => this._unmarkSelected(value)); + const changed = this._hasQueuedChanges(); + this._emitChangeEvent(); + return changed; } /** @@ -157,38 +159,6 @@ export class SelectionModel { return this._multiple; } - /** Selects an array of values. */ - private _select(values: T[]) { - this._verifyValueAssignment(values); - values.forEach(value => this._markSelected(value)); - const changed = this._hasQueuedChanges(); - this._emitChangeEvent(); - return changed; - } - - /** Deselects an array of values. */ - private _deselect(values: T[]) { - this._verifyValueAssignment(values); - values.forEach(value => this._unmarkSelected(value)); - const changed = this._hasQueuedChanges(); - this._emitChangeEvent(); - return changed; - } - - /** Sets the current selection from an array of items. */ - private _setSelection(values: T[]) { - this._verifyValueAssignment(values); - const oldValues = this.selected; - const newSelectedSet = new Set(values.map(value => this._getConcreteValue(value))); - values.forEach(value => this._markSelected(value)); - oldValues - .filter(value => !newSelectedSet.has(this._getConcreteValue(value, newSelectedSet))) - .forEach(value => this._unmarkSelected(value)); - const changed = this._hasQueuedChanges(); - this._emitChangeEvent(); - return changed; - } - /** Emits a change event and clears the records of selected and deselected values. */ private _emitChangeEvent() { // Clear the selected values so they can be re-cached. diff --git a/src/cdk/collections/selection.spec.ts b/src/cdk/collections/selection.spec.ts index 0418fca6f707..be902b68a478 100644 --- a/src/cdk/collections/selection.spec.ts +++ b/src/cdk/collections/selection.spec.ts @@ -224,59 +224,6 @@ describe('SelectionModel', () => { }); }); - describe('bulk actions', () => { - let model: SelectionModel; - - beforeEach(() => (model = new SelectionModel(true))); - - it('should be able to bulk select values', () => { - const changedSpy = jasmine.createSpy('changed spy'); - - model.changed.subscribe(changedSpy); - model.bulk.select([1, 2]); - - expect(model.selected.length).toBe(2); - expect(model.isSelected(1)).toBe(true); - expect(model.isSelected(2)).toBe(true); - expect(changedSpy).toHaveBeenCalledTimes(1); - }); - - it('should be able to bulk deselect values', () => { - model.bulk.select([1, 2]); - expect(model.selected.length).toBe(2); - expect(model.isSelected(1)).toBe(true); - expect(model.isSelected(2)).toBe(true); - - const changedSpy = jasmine.createSpy('changed spy'); - model.changed.subscribe(changedSpy); - model.bulk.deselect([1, 2]); - - expect(model.selected.length).toBe(0); - expect(model.isSelected(1)).toBe(false); - expect(model.isSelected(2)).toBe(false); - expect(changedSpy).toHaveBeenCalledTimes(1); - }); - - it('should be able to bulk set the selection', () => { - model.bulk.setSelection([1, 2]); - expect(model.selected.length).toBe(2); - expect(model.isSelected(1)).toBe(true); - expect(model.isSelected(2)).toBe(true); - - model.bulk.setSelection([2]); - expect(model.selected.length).toBe(1); - expect(model.isSelected(1)).toBe(false); - expect(model.isSelected(2)).toBe(true); - - model.bulk.setSelection([3, 4]); - expect(model.selected.length).toBe(2); - expect(model.isSelected(1)).toBe(false); - expect(model.isSelected(2)).toBe(false); - expect(model.isSelected(3)).toBe(true); - expect(model.isSelected(4)).toBe(true); - }); - }); - it('should be able to determine whether it is empty', () => { let model = new SelectionModel(); diff --git a/src/cdk/collections/unique-selection-dispatcher.ts b/src/cdk/collections/unique-selection-dispatcher.ts index 11379a8a787a..43dc23fd277c 100644 --- a/src/cdk/collections/unique-selection-dispatcher.ts +++ b/src/cdk/collections/unique-selection-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy} from '@angular/core'; +import {Injectable, OnDestroy} from '@angular/core'; // Users of the Dispatcher never need to see this type, but TypeScript requires it to be exported. export type UniqueSelectionDispatcherListener = (id: string, name: string) => void; @@ -20,7 +20,7 @@ export type UniqueSelectionDispatcherListener = (id: string, name: string) => vo * This service does not *store* any IDs and names because they may change at any time, so it is * less error-prone if they are simply passed through when the events occur. */ -@Service() +@Injectable({providedIn: 'root'}) export class UniqueSelectionDispatcher implements OnDestroy { private _listeners: UniqueSelectionDispatcherListener[] = []; diff --git a/src/cdk/dialog/dialog-config.ts b/src/cdk/dialog/dialog-config.ts index 345238760497..4585b11e4ba0 100644 --- a/src/cdk/dialog/dialog-config.ts +++ b/src/cdk/dialog/dialog-config.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ViewContainerRef, Injector, StaticProvider, Type, Binding} from '@angular/core'; +import {ViewContainerRef, Injector, StaticProvider, Type} from '@angular/core'; import {Direction} from '../bidi'; import {PositionStrategy, ScrollStrategy} from '../overlay'; import {Observable} from 'rxjs'; @@ -16,16 +16,6 @@ import {FocusOrigin} from '../a11y'; /** Options for where to set focus to automatically on dialog open */ export type AutoFocusTarget = 'dialog' | 'first-tabbable' | 'first-heading'; -/** - * Value that determines the focus restoration behavior for a dialog. - * The values represent the following behaviors: - * - `boolean` - when true, will return focus to the element that was focused before the dialog - * was opened, otherwise won't restore focus at all. - * - `string` - focus will be restored to the first element that matches the CSS selector. - * - `HTMLElement` - focus will be restored to the specific element. - */ -export type RestoreFocusValue = boolean | string | HTMLElement; - /** Valid ARIA roles for a dialog. */ export type DialogRole = 'dialog' | 'alertdialog'; @@ -131,8 +121,15 @@ export class DialogConfig | (() => Record); - - /** - * Bindings to apply to the component rendered inside the dialog. - * Does nothing for template-based dialogs. - */ - bindings?: Binding[]; } diff --git a/src/cdk/dialog/dialog-container.ts b/src/cdk/dialog/dialog-container.ts index 2de483206a06..5ef4296b59e5 100644 --- a/src/cdk/dialog/dialog-container.ts +++ b/src/cdk/dialog/dialog-container.ts @@ -57,7 +57,7 @@ export function throwDialogContentAlreadyAttachedError() { encapsulation: ViewEncapsulation.None, // Using OnPush for dialogs caused some G3 sync issues. Disabled until we can track them down. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [CdkPortalOutlet], host: { 'class': 'cdk-dialog-container', @@ -114,6 +114,8 @@ export class CdkDialogContainer private _isDestroyed = false; + constructor(...args: unknown[]); + constructor() { super(); diff --git a/src/cdk/dialog/dialog.md b/src/cdk/dialog/dialog.md index a324589660ff..b5b7ae84841d 100644 --- a/src/cdk/dialog/dialog.md +++ b/src/cdk/dialog/dialog.md @@ -50,11 +50,9 @@ in which they are contained. When closing, an optional result value can be provi value is forwarded as the result of the `closed` Observable. ```ts -import {inject} from '@angular/core'; - @Component({/* ... */}) export class YourDialog { - dialogRef = inject>(DialogRef); + constructor(public dialogRef: DialogRef) {} closeDialog() { this.dialogRef.close('Pizza!'); @@ -119,28 +117,16 @@ class MyDialogContainer extends CdkDialogContainer {} ### Specifying global configuration defaults Default dialog options can be specified by providing an instance of `DialogConfig` for -`DEFAULT_DIALOG_CONFIG` in your app config. +`DEFAULT_DIALOG_CONFIG` in your application's root module. ```ts -bootstrapApplication(MyApp, { +@NgModule({ providers: [ {provide: DEFAULT_DIALOG_CONFIG, useValue: {hasBackdrop: false}} ] -}); +}) ``` -> **Note:** The value provided for `DEFAULT_DIALOG_CONFIG` **replaces** the built-in defaults -> entirely rather than merging with them. For example, providing `{disableClose: true}` means that -> all other defaults (such as `hasBackdrop`) will be `undefined`. If you only want to override -> specific properties, spread the defaults first: -> -> ```ts -> {provide: DEFAULT_DIALOG_CONFIG, useValue: {...new DialogConfig(), disableClose: true}} -> ``` -> -> When you call `dialog.open()` with a config, that config is merged on top of these defaults, so -> per-dialog options always take precedence. - ### Sharing data with the Dialog component. You can use the `data` option to pass information to the dialog component. @@ -153,7 +139,7 @@ const dialogRef = dialog.open(YourDialog, { Access the data in your dialog component with the `DIALOG_DATA` injection token: ```ts -import {Component, inject} from '@angular/core'; +import {Component, Inject} from '@angular/core'; import {DIALOG_DATA} from '@angular/cdk/dialog'; @Component({ @@ -161,7 +147,7 @@ import {DIALOG_DATA} from '@angular/cdk/dialog'; template: 'passed in {{ data.name }}', }) export class YourDialog { - data = inject<{name: string}>(DIALOG_DATA); + constructor(@Inject(DIALOG_DATA) public data: {name: string}) { } } ``` diff --git a/src/cdk/dialog/dialog.spec.ts b/src/cdk/dialog/dialog.spec.ts index 0e588718e3d2..1d67421c468f 100644 --- a/src/cdk/dialog/dialog.spec.ts +++ b/src/cdk/dialog/dialog.spec.ts @@ -17,15 +17,20 @@ import { Directive, InjectionToken, Injector, - Input, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation, inject, - inputBinding, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + flushMicrotasks, + tick, +} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {DIALOG_DATA, Dialog, DialogModule, DialogRef} from './index'; @@ -39,7 +44,7 @@ describe('Dialog', () => { let mockLocation: SpyLocation; let scrolledSubject = new Subject(); - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ {provide: Location, useClass: SpyLocation}, @@ -58,7 +63,7 @@ describe('Dialog', () => { viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; - }); + })); it('should open a dialog with a component', () => { let dialogRef = dialog.open(PizzaMsg, { @@ -171,7 +176,7 @@ describe('Dialog', () => { expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element'); }); - it('should close a dialog and get back a result', () => { + it('should close a dialog and get back a result', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); let afterCloseCallback = jasmine.createSpy('afterClose callback'); @@ -179,12 +184,13 @@ describe('Dialog', () => { dialogRef.closed.subscribe(afterCloseCallback); dialogRef.close('Charmander'); viewContainerFixture.detectChanges(); + flush(); expect(afterCloseCallback).toHaveBeenCalledWith('Charmander'); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeNull(); - }); + })); - it('should only emit the afterCloseEvent once when closed', () => { + it('should only emit the afterCloseEvent once when closed', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); let afterCloseCallback = jasmine.createSpy('afterClose callback'); @@ -192,34 +198,37 @@ describe('Dialog', () => { dialogRef.closed.subscribe(afterCloseCallback); dialogRef.close(); viewContainerFixture.detectChanges(); + flush(); expect(afterCloseCallback).toHaveBeenCalledTimes(1); - }); + })); - it('should close a dialog via the escape key', () => { + it('should close a dialog via the escape key', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeNull(); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not close a dialog via the escape key if a modifier is pressed', () => { + it('should not close a dialog via the escape key if a modifier is pressed', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true}); dispatchEvent(document.body, event); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy(); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should close from a ViewContainerRef with OnPush change detection', () => { + it('should close from a ViewContainerRef with OnPush change detection', fakeAsync(() => { const onPushFixture = TestBed.createComponent(ComponentWithOnPushViewContainer); onPushFixture.detectChanges(); @@ -228,21 +237,25 @@ describe('Dialog', () => { viewContainerRef: onPushFixture.componentInstance.viewContainerRef, }); + flushMicrotasks(); onPushFixture.detectChanges(); + flushMicrotasks(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length) .withContext('Expected one open dialog.') .toBe(1); dialogRef.close(); + flushMicrotasks(); onPushFixture.detectChanges(); + tick(500); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length) .withContext('Expected no open dialogs.') .toBe(0); - }); + })); - it('should close when clicking on the overlay backdrop', () => { + it('should close when clicking on the overlay backdrop', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); @@ -250,11 +263,12 @@ describe('Dialog', () => { backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeFalsy(); - }); + })); - it('should emit the backdropClick stream when clicking on the overlay backdrop', () => { + it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, // Disable closing so the backdrop doesn't go away immediately. @@ -263,29 +277,31 @@ describe('Dialog', () => { const spy = jasmine.createSpy('backdropClick spy'); dialogRef.backdropClick.subscribe(spy); viewContainerFixture.detectChanges(); + flush(); const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); - + flush(); expect(spy).toHaveBeenCalledTimes(1); // Additional clicks after the dialog has closed should not be emitted dialogRef.disableClose = false; backdrop.click(); viewContainerFixture.detectChanges(); - + flush(); expect(spy).toHaveBeenCalledTimes(1); - }); + })); - it('should emit the keyboardEvent stream when key events target the overlay', () => { + it('should emit the keyboardEvent stream when key events target the overlay', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('keyboardEvent spy'); dialogRef.keydownEvents.subscribe(spy); viewContainerFixture.detectChanges(); + flush(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let container = overlayContainerElement.querySelector('cdk-dialog-container') as HTMLElement; @@ -295,7 +311,7 @@ describe('Dialog', () => { dispatchKeyboardEvent(container, 'keydown', A); expect(spy).toHaveBeenCalledTimes(3); - }); + })); it('should notify the observers if a dialog has been opened', () => { dialog.afterOpened.subscribe(ref => { @@ -307,7 +323,7 @@ describe('Dialog', () => { }); }); - it('should notify the observers if all open dialogs have finished closing', () => { + it('should notify the observers if all open dialogs have finished closing', fakeAsync(() => { const ref1 = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const ref2 = dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef, @@ -319,15 +335,16 @@ describe('Dialog', () => { ref1.close(); viewContainerFixture.detectChanges(); + flush(); expect(spy).not.toHaveBeenCalled(); ref2.close(); viewContainerFixture.detectChanges(); - + flush(); viewContainerFixture.detectChanges(); expect(spy).toHaveBeenCalled(); - }); + })); it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => { const spy = jasmine.createSpy('afterAllClosed spy'); @@ -373,7 +390,7 @@ describe('Dialog', () => { expect(overlayPane.style.minWidth).toBe('500px'); }); - it('should override the max-width of the overlay pane', () => { + it('should override the max-width of the overlay pane', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); @@ -384,17 +401,21 @@ describe('Dialog', () => { dialogRef.close(); + tick(500); viewContainerFixture.detectChanges(); + flushMicrotasks(); dialog.open(PizzaMsg, { maxWidth: '100px', }); + tick(500); viewContainerFixture.detectChanges(); + flushMicrotasks(); overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.maxWidth).toBe('100px'); - }); + })); it('should override the min-height of the overlay pane', () => { dialog.open(PizzaMsg, { @@ -472,7 +493,7 @@ describe('Dialog', () => { expect(dialogRef.componentInstance!.directionality.value).toBe('ltr'); }); - it('should close all of the dialogs', () => { + it('should close all of the dialogs', fakeAsync(() => { dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); dialog.open(PizzaMsg); @@ -484,11 +505,12 @@ describe('Dialog', () => { dialog.closeAll(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length).toBe(0); - }); + })); - it('should close all dialogs when the user goes forwards/backwards in history', () => { + it('should close all dialogs when the user goes forwards/backwards in history', fakeAsync(() => { const closeSpy = jasmine.createSpy('closed'); dialog.open(PizzaMsg).closed.subscribe(closeSpy); viewContainerFixture.detectChanges(); @@ -500,12 +522,13 @@ describe('Dialog', () => { mockLocation.simulateUrlPop(''); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length).toBe(0); expect(closeSpy).toHaveBeenCalledTimes(2); - }); + })); - it('should close all open dialogs when the location hash changes', () => { + it('should close all open dialogs when the location hash changes', fakeAsync(() => { const closeSpy = jasmine.createSpy('closed'); dialog.open(PizzaMsg).closed.subscribe(closeSpy); viewContainerFixture.detectChanges(); @@ -516,11 +539,12 @@ describe('Dialog', () => { mockLocation.simulateHashChange(''); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length).toBe(0); - }); + })); - it('should close the dialog when detached externally', () => { + it('should close the dialog when detached externally', fakeAsync(() => { const closeSpy = jasmine.createSpy('closed'); const injector = TestBed.inject(Injector); dialog @@ -537,16 +561,19 @@ describe('Dialog', () => { scrolledSubject.next(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length).toBe(0); expect(closeSpy).toHaveBeenCalledTimes(2); - }); + })); - it('should have the componentInstance available in the afterClosed callback', () => { + it('should have the componentInstance available in the afterClosed callback', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); let spy = jasmine.createSpy('afterClosed spy'); + flushMicrotasks(); viewContainerFixture.detectChanges(); + flushMicrotasks(); dialogRef.closed.subscribe(() => { spy(); @@ -554,13 +581,15 @@ describe('Dialog', () => { dialogRef.close(); + flushMicrotasks(); viewContainerFixture.detectChanges(); + tick(500); // Ensure that the callback actually fires. expect(spy).toHaveBeenCalled(); - }); + })); - it('should close all open dialogs on destroy', () => { + it('should close all open dialogs on destroy', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); @@ -569,11 +598,12 @@ describe('Dialog', () => { dialog.ngOnDestroy(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('cdk-dialog-container').length).toBe(0); - }); + })); - it('should complete the closed stream on destroy', () => { + it('should complete the closed stream on destroy', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); let closedCompleteSpy = jasmine.createSpy('closed complete spy'); @@ -582,9 +612,10 @@ describe('Dialog', () => { dialogRef.close('Charmander'); viewContainerFixture.detectChanges(); + flush(); expect(closedCompleteSpy).toHaveBeenCalled(); - }); + })); describe('passing in data', () => { it('should be able to pass in data', () => { @@ -608,31 +639,23 @@ describe('Dialog', () => { expect(dialogRef.componentInstance!.data).toBeNull(); }).not.toThrow(); }); - - it('should be able to apply bindings', () => { - const dialogRef = dialog.open(PizzaMsg, { - bindings: [inputBinding('flavor', () => 'pepperoni')], - }); - viewContainerFixture.detectChanges(); - - expect(dialogRef.componentInstance!.flavor).toBe('pepperoni'); - }); }); - it('should not keep a reference to the component after the dialog is closed', () => { + it('should not keep a reference to the component after the dialog is closed', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); expect(dialogRef.componentInstance).toBeTruthy(); dialogRef.close(); - + flush(); viewContainerFixture.detectChanges(); + flush(); expect(dialogRef.componentInstance) .withContext('Expected reference to have been cleared.') .toBeFalsy(); - }); + })); it('should assign a unique id to each dialog', () => { const one = dialog.open(PizzaMsg); @@ -658,7 +681,7 @@ describe('Dialog', () => { expect(dialog.getDialogById('pizza')).toBe(dialogRef); }); - it('should recapture focus to the first tabbable element when clicking on the backdrop', () => { + it('should recapture focus to the first tabbable element when clicking on the backdrop', fakeAsync(() => { // When testing focus, all of the elements must be in the DOM. document.body.appendChild(overlayContainerElement); @@ -668,6 +691,7 @@ describe('Dialog', () => { }); viewContainerFixture.detectChanges(); + flushMicrotasks(); const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; const input = overlayContainerElement.querySelector('input') as HTMLInputElement; @@ -677,16 +701,17 @@ describe('Dialog', () => { input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); overlayContainerElement.remove(); - }); + })); describe('disableClose option', () => { - it('should prevent closing via clicks on the backdrop', () => { + it('should prevent closing via clicks on the backdrop', fakeAsync(() => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, @@ -697,11 +722,12 @@ describe('Dialog', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy(); - }); + })); - it('should prevent closing via the escape key', () => { + it('should prevent closing via the escape key', fakeAsync(() => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, @@ -710,11 +736,12 @@ describe('Dialog', () => { viewContainerFixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy(); - }); + })); - it('should allow for the disableClose option to be updated while open', () => { + it('should allow for the disableClose option to be updated while open', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, @@ -730,11 +757,12 @@ describe('Dialog', () => { dialogRef.disableClose = false; backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeFalsy(); - }); + })); - it('should work when opening from a template', () => { + it('should work when opening from a template', fakeAsync(() => { const templateRefFixture = TestBed.createComponent(ComponentWithTemplateRef); templateRefFixture.detectChanges(); @@ -747,24 +775,27 @@ describe('Dialog', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); templateRefFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeTruthy(); - }); + })); it( 'should fall back to node injector in template dialog if token does not exist in ' + 'template injector', - () => { + fakeAsync(() => { const templateInjectFixture = TestBed.createComponent(TemplateInjectorParentComponent); templateInjectFixture.detectChanges(); + flush(); dialog.open(templateInjectFixture.componentInstance.templateRef); templateInjectFixture.detectChanges(); + flush(); expect(templateInjectFixture.componentInstance.innerComponentValue).toBe( 'hello from parent component', ); - }, + }), ); }); @@ -773,7 +804,7 @@ describe('Dialog', () => { return overlayContainerElement.querySelectorAll('cdk-dialog-container'); } - it('should determine whether closing via the backdrop is allowed', () => { + it('should determine whether closing via the backdrop is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -789,6 +820,7 @@ describe('Dialog', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -796,12 +828,13 @@ describe('Dialog', () => { canClose = true; backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should determine whether closing via the escape key is allowed', () => { + it('should determine whether closing via the escape key is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -816,6 +849,7 @@ describe('Dialog', () => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -823,12 +857,13 @@ describe('Dialog', () => { canClose = true; dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should determine whether closing via the `close` method is allowed', () => { + it('should determine whether closing via the `close` method is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -843,6 +878,7 @@ describe('Dialog', () => { ref.close(); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -850,13 +886,14 @@ describe('Dialog', () => { canClose = true; ref.close('hello'); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); expect(closedSpy).toHaveBeenCalledWith('hello'); - }); + })); - it('should not be closed by `closeAll` if not allowed by the predicate', () => { + it('should not be closed by `closeAll` if not allowed by the predicate', fakeAsync(() => { let canClose = false; const config = {closePredicate: () => canClose}; const spy = jasmine.createSpy('afterAllClosed spy'); @@ -873,6 +910,7 @@ describe('Dialog', () => { dialog.closeAll(); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(3); expect(dialog.openDialogs.length).toBe(3); @@ -881,15 +919,16 @@ describe('Dialog', () => { canClose = true; dialog.closeAll(); viewContainerFixture.detectChanges(); + flush(); expect(getDialogs().length).toBe(0); expect(dialog.openDialogs.length).toBe(0); expect(spy).toHaveBeenCalledTimes(1); subscription.unsubscribe(); - }); + })); - it('should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence', () => { + it('should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence', fakeAsync(() => { // When testing focus, all of the elements must be in the DOM. document.body.appendChild(overlayContainerElement); @@ -899,6 +938,7 @@ describe('Dialog', () => { }); viewContainerFixture.detectChanges(); + flushMicrotasks(); const backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', @@ -912,13 +952,14 @@ describe('Dialog', () => { input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); overlayContainerElement.remove(); - }); + })); }); describe('hasBackdrop option', () => { @@ -987,25 +1028,27 @@ describe('Dialog', () => { beforeEach(() => document.body.appendChild(overlayContainerElement)); afterEach(() => overlayContainerElement.remove()); - it('should focus the first tabbable element of the dialog on open (the default)', () => { + it('should focus the first tabbable element of the dialog on open (the default)', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.tagName) .withContext('Expected first tabbable element (input) in the dialog to be focused.') .toBe('INPUT'); - }); + })); - it('should focus the dialog element on open', () => { + it('should focus the dialog element on open', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'dialog', }); viewContainerFixture.detectChanges(); + flushMicrotasks(); let container = overlayContainerElement.querySelector( 'cdk-dialog-container', @@ -1014,15 +1057,16 @@ describe('Dialog', () => { expect(document.activeElement) .withContext('Expected container to be focused on open') .toBe(container); - }); + })); - it('should focus the first header element on open', () => { + it('should focus the first header element on open', fakeAsync(() => { dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef, autoFocus: 'first-heading', }); viewContainerFixture.detectChanges(); + flushMicrotasks(); let firstHeader = overlayContainerElement.querySelector( 'h2[tabindex="-1"]', @@ -1031,15 +1075,16 @@ describe('Dialog', () => { expect(document.activeElement) .withContext('Expected first header to be focused on open') .toBe(firstHeader); - }); + })); - it('should focus the first element that matches the css selector from autoFocus on open', () => { + it('should focus the first element that matches the css selector from autoFocus on open', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'p', }); viewContainerFixture.detectChanges(); + flushMicrotasks(); let firstParagraph = overlayContainerElement.querySelector( 'p[tabindex="-1"]', @@ -1048,9 +1093,9 @@ describe('Dialog', () => { expect(document.activeElement) .withContext('Expected first paragraph to be focused on open') .toBe(firstParagraph); - }); + })); - it('should re-focus trigger element when dialog closes', () => { + it('should re-focus trigger element when dialog closes', fakeAsync(() => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1060,6 +1105,7 @@ describe('Dialog', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', @@ -1068,15 +1114,16 @@ describe('Dialog', () => { dialogRef.close(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id) .withContext('Expected that the trigger was refocused after the dialog is closed.') .toBe('dialog-trigger'); button.remove(); - }); + })); - it('should re-focus trigger element inside the shadow DOM when dialog closes', () => { + it('should re-focus trigger element inside the shadow DOM when dialog closes', fakeAsync(() => { if (!_supportsShadowDom()) { return; } @@ -1084,23 +1131,25 @@ describe('Dialog', () => { viewContainerFixture.destroy(); const fixture = TestBed.createComponent(ShadowDomComponent); fixture.detectChanges(); - + flush(); const button = fixture.debugElement.query(By.css('button'))!.nativeElement; button.focus(); const dialogRef = dialog.open(PizzaMsg); fixture.detectChanges(); + flush(); const spy = spyOn(button, 'focus').and.callThrough(); dialogRef.close(); - + flush(); fixture.detectChanges(); + tick(500); expect(spy).toHaveBeenCalled(); - }); + })); - it('should allow the consumer to shift focus in afterClosed', () => { + it('should allow the consumer to shift focus in afterClosed', fakeAsync(() => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); let input = document.createElement('input'); @@ -1113,11 +1162,16 @@ describe('Dialog', () => { button.focus(); let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); + + tick(500); viewContainerFixture.detectChanges(); dialogRef.closed.subscribe(() => input.focus()); dialogRef.close(); + + tick(500); viewContainerFixture.detectChanges(); + flushMicrotasks(); expect(document.activeElement!.id) .withContext('Expected that the trigger was refocused after the dialog is closed.') @@ -1125,19 +1179,21 @@ describe('Dialog', () => { button.remove(); input.remove(); - }); + flush(); + })); - it('should move focus to the container if there are no focusable elements in the dialog', () => { + it('should move focus to the container if there are no focusable elements in the dialog', fakeAsync(() => { dialog.open(DialogWithoutFocusableElements); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.tagName.toLowerCase()) .withContext('Expected dialog container to be focused.') .toBe('cdk-dialog-container'); - }); + })); - it('should allow for focus restoration to be disabled', () => { + it('should allow for focus restoration to be disabled', fakeAsync(() => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1150,17 +1206,19 @@ describe('Dialog', () => { }); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id).not.toBe('dialog-trigger'); dialogRef.close(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id).not.toBe('dialog-trigger'); button.remove(); - }); + })); - it('should allow for focus to be restored to an element matching a selector', () => { + it('should allow for focus to be restored to an element matching a selector', fakeAsync(() => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1171,18 +1229,22 @@ describe('Dialog', () => { restoreFocus: `#${button.id}`, }); + flushMicrotasks(); viewContainerFixture.detectChanges(); + flushMicrotasks(); expect(document.activeElement!.id).not.toBe('dialog-trigger'); dialogRef.close(); + flushMicrotasks(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id).toBe('dialog-trigger'); button.remove(); - }); + })); - it('should allow for focus to be restored to a specific DOM node', () => { + it('should allow for focus to be restored to a specific DOM node', fakeAsync(() => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1193,16 +1255,20 @@ describe('Dialog', () => { restoreFocus: button, }); + flushMicrotasks(); viewContainerFixture.detectChanges(); + flushMicrotasks(); expect(document.activeElement!.id).not.toBe('dialog-trigger'); dialogRef.close(); + flushMicrotasks(); viewContainerFixture.detectChanges(); + flush(); expect(document.activeElement!.id).toBe('dialog-trigger'); button.remove(); - }); + })); }); describe('aria-label', () => { @@ -1217,17 +1283,18 @@ describe('Dialog', () => { expect(container.getAttribute('aria-label')).toBe('Hello there'); }); - it('should not set the aria-labelledby automatically if it has an aria-label', () => { + it('should not set the aria-labelledby automatically if it has an aria-label', fakeAsync(() => { dialog.open(ContentElementDialog, { ariaLabel: 'Hello there', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); + tick(); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('cdk-dialog-container')!; expect(container.hasAttribute('aria-labelledby')).toBe(false); - }); + })); }); }); @@ -1237,7 +1304,7 @@ describe('Dialog with a parent Dialog', () => { let overlayContainerElement: HTMLElement; let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ { @@ -1255,15 +1322,16 @@ describe('Dialog with a parent Dialog', () => { fixture = TestBed.createComponent(ComponentThatProvidesMatDialog); childDialog = fixture.componentInstance.dialog; fixture.detectChanges(); - }); + })); afterEach(() => { overlayContainerElement.innerHTML = ''; }); - it('should close dialogs opened by a parent when calling closeAll on a child Dialog', () => { + it('should close dialogs opened by a parent when calling closeAll on a child Dialog', fakeAsync(() => { parentDialog.open(PizzaMsg); fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') @@ -1271,13 +1339,14 @@ describe('Dialog with a parent Dialog', () => { childDialog.closeAll(); fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on child Dialog to close dialog opened by parent') .toBe(''); - }); + })); - it('should close dialogs opened by a child when calling closeAll on a parent Dialog', () => { + it('should close dialogs opened by a child when calling closeAll on a parent Dialog', fakeAsync(() => { childDialog.open(PizzaMsg); fixture.detectChanges(); @@ -1287,15 +1356,17 @@ describe('Dialog with a parent Dialog', () => { parentDialog.closeAll(); fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on parent Dialog to close dialog opened by child') .toBe(''); - }); + })); - it('should not close the parent dialogs, when a child is destroyed', () => { + it('should not close the parent dialogs, when a child is destroyed', fakeAsync(() => { parentDialog.open(PizzaMsg); fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') @@ -1303,21 +1374,23 @@ describe('Dialog with a parent Dialog', () => { childDialog.ngOnDestroy(); fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to remain opened') .toContain('Pizza'); - }); + })); - it('should close the top dialog via the escape key', () => { + it('should close the top dialog via the escape key', fakeAsync(() => { childDialog.open(PizzaMsg); fixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('cdk-dialog-container')).toBeNull(); - }); + })); }); @Directive({ @@ -1328,6 +1401,7 @@ class DirectiveWithViewContainer { } @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, template: 'hello', }) class ComponentWithOnPushViewContainer { @@ -1338,7 +1412,6 @@ class ComponentWithOnPushViewContainer { selector: 'arbitrary-component', template: ``, imports: [DirectiveWithViewContainer], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithChildViewContainer { @ViewChild(DirectiveWithViewContainer) childWithViewContainer!: DirectiveWithViewContainer; @@ -1353,7 +1426,6 @@ class ComponentWithChildViewContainer { template: ` Cheese {{localValue}} {{data?.value}}{{setDialogRef(dialogRef)}}`, imports: [DialogModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithTemplateRef { localValue = ''; @@ -1371,10 +1443,8 @@ class ComponentWithTemplateRef { @Component({ template: '

    Pizza

    ', imports: [DialogModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg { - @Input() flavor = 'unknown'; dialogRef = inject>(DialogRef); dialogInjector = inject(Injector); directionality = inject(Directionality); @@ -1389,7 +1459,6 @@ class PizzaMsg { // Avoids conflicting ID warning 'id': 'content-element-dialog', }, - changeDetection: ChangeDetectionStrategy.Eager, }) class ContentElementDialog { closeButtonAriaLabel = ''; @@ -1399,7 +1468,6 @@ class ContentElementDialog { template: '', providers: [Dialog], imports: [DialogModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentThatProvidesMatDialog { dialog = inject(Dialog); @@ -1409,7 +1477,6 @@ class ComponentThatProvidesMatDialog { @Component({ template: '', imports: [DialogModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithInjectedData { data = inject(DIALOG_DATA); @@ -1422,14 +1489,12 @@ class DialogWithInjectedData { // Avoids conflicting ID warning 'id': 'dialog-without-focusable', }, - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithoutFocusableElements {} @Component({ template: ``, encapsulation: ViewEncapsulation.ShadowDom, - changeDetection: ChangeDetectionStrategy.Eager, }) class ShadowDomComponent {} @@ -1454,7 +1519,6 @@ class TemplateInjectorInnerDirective { }, ], imports: [TemplateInjectorInnerDirective], - changeDetection: ChangeDetectionStrategy.Eager, }) class TemplateInjectorParentComponent { @ViewChild(TemplateRef) templateRef!: TemplateRef; diff --git a/src/cdk/dialog/dialog.ts b/src/cdk/dialog/dialog.ts index c1f93089e21c..93ea177750bf 100644 --- a/src/cdk/dialog/dialog.ts +++ b/src/cdk/dialog/dialog.ts @@ -9,7 +9,7 @@ import { ComponentRef, EventEmitter, - Service, + Injectable, Injector, OnDestroy, StaticProvider, @@ -52,7 +52,7 @@ function getDirectionality(value: Direction): Directionality { }; } -@Service() +@Injectable({providedIn: 'root'}) export class Dialog implements OnDestroy { private _injector = inject(Injector); private _defaultOptions = inject(DEFAULT_DIALOG_CONFIG, {optional: true}); @@ -86,6 +86,10 @@ export class Dialog implements OnDestroy { : this._getAfterAllClosed().pipe(startWith(undefined)), ); + constructor(...args: unknown[]); + + constructor() {} + /** * Opens a modal dialog containing the given component. * @param component Type of the component to load into the dialog. @@ -302,13 +306,7 @@ export class Dialog implements OnDestroy { } else { const injector = this._createInjector(config, dialogRef, dialogContainer, this._injector); const contentRef = dialogContainer.attachComponentPortal( - new ComponentPortal( - componentOrTemplateRef, - config.viewContainerRef, - injector, - null, - config.bindings, - ), + new ComponentPortal(componentOrTemplateRef, config.viewContainerRef, injector), ); (dialogRef as {componentRef: ComponentRef}).componentRef = contentRef; (dialogRef as {componentInstance: C}).componentInstance = contentRef.instance; diff --git a/src/cdk/drag-drop/directives/drag-handle.ts b/src/cdk/drag-drop/directives/drag-handle.ts index 488ad6e1803d..01d11aa430c8 100644 --- a/src/cdk/drag-drop/directives/drag-handle.ts +++ b/src/cdk/drag-drop/directives/drag-handle.ts @@ -57,6 +57,8 @@ export class CdkDragHandle implements AfterViewInit, OnDestroy { } private _disabled = false; + constructor(...args: unknown[]); + constructor() { if (typeof ngDevMode === 'undefined' || ngDevMode) { assertElementNode(this.element.nativeElement, 'cdkDragHandle'); diff --git a/src/cdk/drag-drop/directives/drag-placeholder.ts b/src/cdk/drag-drop/directives/drag-placeholder.ts index bf86a7155c0f..7f9a916a0b56 100644 --- a/src/cdk/drag-drop/directives/drag-placeholder.ts +++ b/src/cdk/drag-drop/directives/drag-placeholder.ts @@ -32,6 +32,8 @@ export class CdkDragPlaceholder implements OnDestroy { /** Context data to be added to the placeholder template instance. */ @Input() data!: T; + constructor(...args: unknown[]); + constructor() { this._drag?._setPlaceholderTemplate(this); } diff --git a/src/cdk/drag-drop/directives/drag-preview.ts b/src/cdk/drag-drop/directives/drag-preview.ts index c0d7e1816ecc..10131d5b4ff8 100644 --- a/src/cdk/drag-drop/directives/drag-preview.ts +++ b/src/cdk/drag-drop/directives/drag-preview.ts @@ -43,6 +43,8 @@ export class CdkDragPreview implements OnDestroy { /** Whether the preview should preserve the same size as the item that is being dragged. */ @Input({transform: booleanAttribute}) matchSize: boolean = false; + constructor(...args: unknown[]); + constructor() { this._drag?._setPreviewTemplate(this); } diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index 886cfbeac622..6b538025dfdf 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -216,6 +216,8 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { private _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() { const dropContainer = this.dropContainer; const config = inject(CDK_DRAG_CONFIG, {optional: true}); @@ -313,7 +315,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { ); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const rootSelectorChange = changes['rootElementSelector']; const positionChange = changes['freeDragPosition']; diff --git a/src/cdk/drag-drop/directives/drop-list-group.ts b/src/cdk/drag-drop/directives/drop-list-group.ts index c8b699c9045f..89aedb1553f8 100644 --- a/src/cdk/drag-drop/directives/drop-list-group.ts +++ b/src/cdk/drag-drop/directives/drop-list-group.ts @@ -6,15 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Directive, InjectionToken, Input, OnDestroy, booleanAttribute} from '@angular/core'; -import type {CdkDropList} from './drop-list'; +import {Directive, OnDestroy, Input, InjectionToken, booleanAttribute} from '@angular/core'; /** * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary * retention of the class and its directive metadata. */ -export const CDK_DROP_LIST_GROUP = new InjectionToken>( +export const CDK_DROP_LIST_GROUP = new InjectionToken>( 'CdkDropListGroup', ); diff --git a/src/cdk/drag-drop/directives/drop-list-shared.spec.ts b/src/cdk/drag-drop/directives/drop-list-shared.spec.ts index 5ddf7741d78e..dda1b75b16c3 100644 --- a/src/cdk/drag-drop/directives/drop-list-shared.spec.ts +++ b/src/cdk/drag-drop/directives/drop-list-shared.spec.ts @@ -13,7 +13,7 @@ import { inject, signal, } from '@angular/core'; -import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {Platform, _supportsShadowDom} from '../../platform'; import {CdkScrollable, ViewportRuler} from '../../scrolling'; import { @@ -31,7 +31,7 @@ import {DragRef, Point, PreviewContainer} from '../drag-ref'; import {moveItemInArray} from '../drag-utils'; import {provideFakeDirectionality} from '@angular/cdk/testing/private/fake-directionality'; -import {NgFor, NgIf, NgTemplateOutlet} from '@angular/common'; +import {NgClass, NgFor, NgIf, NgTemplateOutlet} from '@angular/common'; import {CDK_DRAG_CONFIG, DragAxis, DragDropConfig, DropListOrientation} from './config'; import {CdkDrag} from './drag'; import {CdkDragPlaceholder} from './drag-placeholder'; @@ -47,7 +47,7 @@ import { startDraggingViaMouse, startDraggingViaTouch, stopDraggingViaTouch, - wait, + tickAnimationFrames, } from './test-utils.spec'; export const ITEM_HEIGHT = 25; @@ -162,7 +162,7 @@ export function defineCommonDropListTests(config: { ); }); - it('should be able to overwrite the drop zone id', () => { + it('should be able to overwrite the drop zone id', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.dropZoneId = 'custom-id'; @@ -172,9 +172,9 @@ export function defineCommonDropListTests(config: { expect(drop.id).toBe('custom-id'); expect(drop.element.nativeElement.getAttribute('id')).toBe('custom-id'); - }); + })); - it('should toggle a class when the user starts dragging an item', async () => { + it('should toggle a class when the user starts dragging an item', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -188,13 +188,13 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(dropZone.element.nativeElement.classList).not.toContain('cdk-drop-dragging'); - }); + })); - it('should toggle the drop dragging classes if there is nothing to trigger change detection', async () => { + it('should toggle the drop dragging classes if there is nothing to trigger change detection', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithoutEvents); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -210,14 +210,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(dropZone.element.nativeElement.classList).not.toContain('cdk-drop-dragging'); expect(item.classList).not.toContain('cdk-drag-dragging'); - }); + })); - it('should toggle a class when the user starts dragging an item with OnPush change detection', async () => { + it('should toggle a class when the user starts dragging an item with OnPush change detection', fakeAsync(() => { const fixture = createComponent(DraggableInOnPushDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -231,13 +231,13 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(dropZone.element.nativeElement.classList).not.toContain('cdk-drop-dragging'); - }); + })); - it('should not toggle dragging class if the element was not dragged more than the threshold', () => { + it('should not toggle dragging class if the element was not dragged more than the threshold', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone, {dragDistance: 5}); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -248,9 +248,9 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); expect(dropZone.element.nativeElement.classList).not.toContain('cdk-drop-dragging'); - }); + })); - it('should dispatch the `dropped` event when an item has been dropped', async () => { + it('should dispatch the `dropped` event when an item has been dropped', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -271,7 +271,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -298,9 +298,9 @@ export function defineCommonDropListTests(config: { 'Zero', 'Three', ]); - }); + })); - it('should expose whether an item was dropped over a container', async () => { + it('should expose whether an item was dropped over a container', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -313,7 +313,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -322,16 +322,16 @@ export function defineCommonDropListTests(config: { fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; expect(event.isPointerOverContainer).toBe(true); - }); + })); - it('should expose the drag distance when an item is dropped', async () => { + it('should expose the drag distance when an item is dropped', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; const firstItem = dragItems.first; dragElementViaMouse(fixture, firstItem.element.nativeElement, 50, 60); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -341,9 +341,9 @@ export function defineCommonDropListTests(config: { expect(event.distance).toEqual({x: 50, y: 60}); expect(event.dropPoint).toEqual({x: 50, y: 60}); - }); + })); - it('should expose whether an item was dropped outside of a container', async () => { + it('should expose whether an item was dropped outside of a container', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -357,7 +357,7 @@ export function defineCommonDropListTests(config: { containerRect.right + 10, containerRect.bottom + 10, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -366,9 +366,9 @@ export function defineCommonDropListTests(config: { fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; expect(event.isPointerOverContainer).toBe(false); - }); + })); - it('should dispatch the `sorted` event as an item is being sorted', async () => { + it('should dispatch the `sorted` event as an item is being sorted', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -395,10 +395,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should not dispatch the `sorted` event when an item is dragged inside a single-item list', async () => { + it('should not dispatch the `sorted` event when an item is dragged inside a single-item list', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.items = [fixture.componentInstance.items[0]]; fixture.detectChanges(); @@ -417,10 +417,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should not move items in a vertical list if the pointer is too far away', async () => { + it('should not move items in a vertical list if the pointer is too far away', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -442,7 +442,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.right + 1000, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -469,9 +469,9 @@ export function defineCommonDropListTests(config: { 'Two', 'Three', ]); - }); + })); - it('should not move the original element from its initial DOM position', async () => { + it('should not move the original element from its initial DOM position', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const root = fixture.nativeElement as HTMLElement; @@ -491,14 +491,14 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); dragElements = Array.from(root.querySelectorAll('.cdk-drag')); expect(dragElements.map(el => el.textContent)).toEqual(['Zero', 'One', 'Two', 'Three']); - }); + })); - it('should dispatch the `dropped` event in a horizontal drop zone', async () => { + it('should dispatch the `dropped` event in a horizontal drop zone', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -519,7 +519,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -546,9 +546,9 @@ export function defineCommonDropListTests(config: { 'Zero', 'Three', ]); - }); + })); - it('should dispatch the correct `dropped` event in RTL horizontal drop zone', async () => { + it('should dispatch the correct `dropped` event in RTL horizontal drop zone', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone, { providers: [provideFakeDirectionality('rtl')], }); @@ -573,7 +573,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.right - 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -600,9 +600,9 @@ export function defineCommonDropListTests(config: { 'Zero', 'Three', ]); - }); + })); - it('should not move items in a horizontal list if pointer is too far away', async () => { + it('should not move items in a horizontal list if pointer is too far away', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -624,7 +624,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.bottom + 1000, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -651,9 +651,9 @@ export function defineCommonDropListTests(config: { 'Two', 'Three', ]); - }); + })); - it('should calculate the index if the list is scrolled while dragging', async () => { + it('should calculate the index if the list is scrolled while dragging', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -673,7 +673,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -693,9 +693,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should calculate the index if the list is scrolled while dragging inside the shadow DOM', async () => { + it('should calculate the index if the list is scrolled while dragging inside the shadow DOM', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -722,7 +722,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -742,9 +742,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should calculate the index if the viewport is scrolled while dragging', async () => { + it('should calculate the index if the viewport is scrolled while dragging', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); for (let i = 0; i < 200; i++) { @@ -773,7 +773,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -795,9 +795,9 @@ export function defineCommonDropListTests(config: { }); scrollTo(0, 0); - }); + })); - it('should remove the anchor node once dragging stops', async () => { + it('should remove the anchor node once dragging stops', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -812,12 +812,12 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(anchor!.parentNode).toBeFalsy(); - }); + })); - it('should create a preview element while the item is dragged', async () => { + it('should create a preview element while the item is dragged', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -878,7 +878,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(item.parentNode) .withContext('Expected element to be moved back into its old parent') @@ -892,9 +892,9 @@ export function defineCommonDropListTests(config: { expect(preview.parentNode) .withContext('Expected preview to be removed from the DOM') .toBeFalsy(); - }); + })); - it('should be able to configure the preview z-index', async () => { + it('should be able to configure the preview z-index', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone, { providers: [ { @@ -912,9 +912,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.zIndex).toBe('3000'); - }); + })); - it('should be able to constrain the preview position', async () => { + it('should be able to constrain the preview position', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.boundarySelector = '.cdk-drop-list'; fixture.detectChanges(); @@ -927,7 +927,7 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; startDraggingViaMouse(fixture, item, listRect.right + 50, listRect.bottom + 50); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', listRect.right + 50, listRect.bottom + 50); fixture.detectChanges(); @@ -935,9 +935,9 @@ export function defineCommonDropListTests(config: { expect(Math.floor(previewRect.bottom)).toBe(Math.floor(listRect.bottom)); expect(Math.floor(previewRect.right)).toBe(Math.floor(listRect.right)); - }); + })); - it('should update the boundary if the page is scrolled while dragging', async () => { + it('should update the boundary if the page is scrolled while dragging', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.boundarySelector = '.cdk-drop-list'; fixture.detectChanges(); @@ -950,7 +950,7 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); startDraggingViaMouse(fixture, item, listRect.right, listRect.bottom); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', listRect.right, listRect.bottom); fixture.detectChanges(); @@ -968,9 +968,9 @@ export function defineCommonDropListTests(config: { expect(Math.floor(previewRect.bottom)).toBe(Math.floor(listRect.bottom)); cleanup(); - }); + })); - it('should update the boundary if a parent is scrolled while dragging', async () => { + it('should update the boundary if a parent is scrolled while dragging', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableParentContainer); fixture.componentInstance.boundarySelector = '.cdk-drop-list'; fixture.detectChanges(); @@ -984,7 +984,7 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); startDraggingViaMouse(fixture, item, listRect.right, listRect.bottom); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', listRect.right, listRect.bottom); fixture.detectChanges(); @@ -1005,9 +1005,9 @@ export function defineCommonDropListTests(config: { expect(Math.abs(previewRect.bottom - listRect.bottom)).toBeLessThan(2); cleanup(); - }); + })); - it('should update the boundary if a parent is scrolled while dragging inside the shadow DOM', async () => { + it('should update the boundary if a parent is scrolled while dragging inside the shadow DOM', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -1031,7 +1031,7 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); startDraggingViaMouse(fixture, item, listRect.right, listRect.bottom); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', listRect.right, listRect.bottom); fixture.detectChanges(); @@ -1054,9 +1054,9 @@ export function defineCommonDropListTests(config: { expect(Math.abs(previewRect.bottom - listRect.bottom)).toBeLessThan(2); cleanup(); - }); + })); - it('should clear the id from the preview', async () => { + it('should clear the id from the preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1067,9 +1067,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.getAttribute('id')).toBeFalsy(); - }); + })); - it('should clone the content of descendant canvas elements', async () => { + it('should clone the content of descendant canvas elements', fakeAsync(() => { const fixture = createComponent(DraggableWithCanvasInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1094,9 +1094,8 @@ export function defineCommonDropListTests(config: { expect(previewCanvas.toDataURL()) .withContext('Expected cloned canvas to have the same content as the source.') .toBe(sourceCanvas.toDataURL()); - }); + })); - // TODO(crisbeto): this one is using `fakeAsync` because we throw an error from a subscription. it('should not throw when cloning an invalid canvas', fakeAsync(() => { const fixture = createComponent(DraggableWithInvalidCanvasInDropZone); fixture.detectChanges(); @@ -1110,7 +1109,7 @@ export function defineCommonDropListTests(config: { expect(document.querySelector('.cdk-drag-preview canvas')).toBeTruthy(); })); - it('should clone the content of descendant input elements', () => { + it('should clone the content of descendant input elements', fakeAsync(() => { const fixture = createComponent(DraggableWithInputsInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1133,9 +1132,9 @@ export function defineCommonDropListTests(config: { expect(previewInput.value).toBe(value); expect(previewTextarea.value).toBe(value); expect(previewSelect.value).toBe(value); - }); + })); - it('should preserve checked state for radio inputs in the content', async () => { + it('should preserve checked state for radio inputs in the content', fakeAsync(() => { const fixture = createComponent(DraggableWithRadioInputsInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[2].element.nativeElement; @@ -1161,14 +1160,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); // Important to tick with 0 since we don't want to flush any pending timeouts. // It also makes sure that all clones have been removed from the DOM. - await fixture.whenStable(); + tick(0); expect(sourceRadioInput.checked) .withContext('Expected original radio input has preserved its original checked state') .toBeTruthy(); - }); + })); - it('should clear the ids from descendants of the preview', async () => { + it('should clear the ids from descendants of the preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1181,9 +1180,9 @@ export function defineCommonDropListTests(config: { expect(document.querySelectorAll('.preview-child').length).toBeGreaterThan(1); expect(document.querySelectorAll('[id="child-id"]').length).toBe(1); - }); + })); - it('should not create a preview if the element was not dragged far enough', async () => { + it('should not create a preview if the element was not dragged far enough', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone, {dragDistance: 5}); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1191,9 +1190,9 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); expect(document.querySelector('.cdk-drag-preview')).toBeFalsy(); - }); + })); - it('should pass the proper direction to the preview in rtl', async () => { + it('should pass the proper direction to the preview in rtl', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone, { providers: [provideFakeDirectionality('rtl')], }); @@ -1206,9 +1205,9 @@ export function defineCommonDropListTests(config: { expect(document.querySelector('.cdk-drag-preview')!.getAttribute('dir')) .withContext('Expected preview to inherit the directionality.') .toBe('rtl'); - }); + })); - it('should remove the preview if its `transitionend` event timed out', async () => { + it('should remove the preview if its `transitionend` event timed out', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1226,20 +1225,20 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await wait(250); + tick(250); expect(preview.parentNode) .withContext('Expected preview to be in the DOM mid-way through the transition') .toBeTruthy(); - await wait(500); + tick(500); expect(preview.parentNode) .withContext('Expected preview to be removed from the DOM if the transition timed out') .toBeFalsy(); - }); + })); - it('should be able to set a single class on a preview', async () => { + it('should be able to set a single class on a preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.previewClass = 'custom-class'; fixture.detectChanges(); @@ -1249,9 +1248,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.classList).toContain('custom-class'); - }); + })); - it('should be able to set multiple classes on a preview', async () => { + it('should be able to set multiple classes on a preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.previewClass = ['custom-class-1', 'custom-class-2']; fixture.detectChanges(); @@ -1262,9 +1261,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.classList).toContain('custom-class-1'); expect(preview.classList).toContain('custom-class-2'); - }); + })); - it('should emit the released event as soon as the item is released', async () => { + it('should emit the released event as soon as the item is released', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1]; @@ -1289,16 +1288,16 @@ export function defineCommonDropListTests(config: { // Expected the released event to fire immediately upon release. expect(releasedSpy).toHaveBeenCalled(); - await wait(1000); + tick(1000); // Expected the ended event to fire once the entire sequence is done. expect(endedSpy).toHaveBeenCalled(); endedSubscription.unsubscribe(); releasedSubscription.unsubscribe(); - }); + })); - it('should reset immediately when failed drag happens after a successful one', async () => { + it('should reset immediately when failed drag happens after a successful one', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -1309,7 +1308,7 @@ export function defineCommonDropListTests(config: { // Do an initial drag and drop sequence. dragElementViaMouse(fixture, item, 50, 50); - await fixture.whenStable(); // Important to tick with 0 since we don't want to flush any pending timeouts. + tick(0); // Important to tick with 0 since we don't want to flush any pending timeouts. expect(spy).toHaveBeenCalledTimes(1); @@ -1323,14 +1322,14 @@ export function defineCommonDropListTests(config: { // Dispatch the mouseup immediately to simulate the user not moving the element. dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); // Important to tick with 0 since we don't want to flush any pending timeouts. + tick(0); // Important to tick with 0 since we don't want to flush any pending timeouts. expect(spy).toHaveBeenCalledTimes(2); subscription.unsubscribe(); - }); + })); - it('should not wait for transition that are not on the `transform` property', async () => { + it('should not wait for transition that are not on the `transform` property', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1345,14 +1344,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + tick(0); expect(preview.parentNode) .withContext('Expected preview to be removed from the DOM immediately') .toBeFalsy(); - }); + })); - it('should pick out the `transform` duration if multiple properties are being transitioned', async () => { + it('should pick out the `transform` duration if multiple properties are being transitioned', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1367,22 +1366,22 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await wait(500); + tick(500); expect(preview.parentNode) .withContext('Expected preview to be in the DOM at the end of the opacity transition') .toBeTruthy(); - await wait(1000); + tick(1000); expect(preview.parentNode) .withContext( 'Expected preview to be removed from the DOM at the end of the transform transition', ) .toBeFalsy(); - }); + })); - it('should create a placeholder element while the item is dragged', async () => { + it('should create a placeholder element while the item is dragged', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1405,14 +1404,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(placeholder.parentNode) .withContext('Expected placeholder to be removed from the DOM') .toBeFalsy(); - }); + })); - it('should insert the preview into the `body` if previewContainer is set to `global`', async () => { + it('should insert the preview into the `body` if previewContainer is set to `global`', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.previewContainer = 'global'; fixture.changeDetectorRef.markForCheck(); @@ -1422,9 +1421,9 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); const preview = document.querySelector('.cdk-drag-preview') as HTMLElement; expect(preview.parentNode).toBe(document.body); - }); + })); - it('should insert the preview into the parent node if previewContainer is set to `parent`', async () => { + it('should insert the preview into the parent node if previewContainer is set to `parent`', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.previewContainer = 'parent'; fixture.changeDetectorRef.markForCheck(); @@ -1436,9 +1435,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview') as HTMLElement; expect(list).toBeTruthy(); expect(preview.parentNode).toBe(list); - }); + })); - it('should insert the preview into a particular element, if specified', async () => { + it('should insert the preview into a particular element, if specified', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1452,9 +1451,9 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); const preview = document.querySelector('.cdk-drag-preview') as HTMLElement; expect(preview.parentNode).toBe(previewContainer.nativeElement); - }); + })); - it('should remove the id from the placeholder', async () => { + it('should remove the id from the placeholder', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1466,9 +1465,9 @@ export function defineCommonDropListTests(config: { const placeholder = document.querySelector('.cdk-drag-placeholder')! as HTMLElement; expect(placeholder.getAttribute('id')).toBeFalsy(); - }); + })); - it('should clear the ids from descendants of the placeholder', async () => { + it('should clear the ids from descendants of the placeholder', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1481,9 +1480,9 @@ export function defineCommonDropListTests(config: { expect(document.querySelectorAll('.placeholder-child').length).toBeGreaterThan(1); expect(document.querySelectorAll('[id="child-id"]').length).toBe(1); - }); + })); - it('should not create placeholder if the element was not dragged far enough', () => { + it('should not create placeholder if the element was not dragged far enough', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone, {dragDistance: 5}); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1491,83 +1490,83 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); expect(document.querySelector('.cdk-drag-placeholder')).toBeFalsy(); - }); + })); - it('should move the placeholder as an item is being sorted down', async () => { + it('should move the placeholder as an item is being sorted down', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - await assertStartToEndSorting( + assertStartToEndSorting( 'vertical', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); - it('should move the placeholder as an item is being sorted down on a scrolled page', async () => { + it('should move the placeholder as an item is being sorted down on a scrolled page', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const cleanup = makeScrollable(); scrollTo(0, 5000); - await assertStartToEndSorting( + assertStartToEndSorting( 'vertical', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); cleanup(); - }); + })); - it('should move the placeholder as an item is being sorted up', async () => { + it('should move the placeholder as an item is being sorted up', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - await assertEndToStartSorting( + assertEndToStartSorting( 'vertical', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); - it('should move the placeholder as an item is being sorted up on a scrolled page', async () => { + it('should move the placeholder as an item is being sorted up on a scrolled page', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const cleanup = makeScrollable(); scrollTo(0, 5000); - await assertEndToStartSorting( + assertEndToStartSorting( 'vertical', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); cleanup(); - }); + })); - it('should move the placeholder as an item is being sorted to the right', async () => { + it('should move the placeholder as an item is being sorted to the right', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); - await assertStartToEndSorting( + assertStartToEndSorting( 'horizontal', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); - it('should move the placeholder as an item is being sorted to the left', async () => { + it('should move the placeholder as an item is being sorted to the left', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); - await assertEndToStartSorting( + assertEndToStartSorting( 'horizontal', fixture, config.getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); - it('should lay out the elements correctly, if an element skips multiple positions when sorting vertically', async () => { + it('should lay out the elements correctly, if an element skips multiple positions when sorting vertically', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -1593,10 +1592,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should lay out the elements correctly, if an element skips multiple positions when sorting horizontally', async () => { + it('should lay out the elements correctly, if an element skips multiple positions when sorting horizontally', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); @@ -1619,10 +1618,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should not swap position for tiny pointer movements', async () => { + it('should not swap position for tiny pointer movements', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -1664,10 +1663,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should swap position for pointer movements in the opposite direction', async () => { + it('should swap position for pointer movements in the opposite direction', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -1709,10 +1708,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('it should allow item swaps in the same drag direction, if the pointer did not overlap with the sibling item after the previous swap', async () => { + it('it should allow item swaps in the same drag direction, if the pointer did not overlap with the sibling item after the previous swap', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -1762,10 +1761,10 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should clean up the preview element if the item is destroyed mid-drag', async () => { + it('should clean up the preview element if the item is destroyed mid-drag', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1785,9 +1784,9 @@ export function defineCommonDropListTests(config: { expect(item.parentNode) .withContext('Expected drag item to be removed from the DOM') .toBeFalsy(); - }); + })); - it('should be able to customize the preview element', async () => { + it('should be able to customize the preview element', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1799,12 +1798,12 @@ export function defineCommonDropListTests(config: { expect(preview).toBeTruthy(); expect(preview.classList).toContain('custom-preview'); expect(preview.textContent!.trim()).toContain('Custom preview'); - }); + })); - it('should handle the custom preview being removed', async () => { + it('should handle the custom preview being removed', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; fixture.componentInstance.renderCustomPreview = false; @@ -1817,9 +1816,9 @@ export function defineCommonDropListTests(config: { expect(preview).toBeTruthy(); expect(preview.classList).not.toContain('custom-preview'); expect(preview.textContent!.trim()).not.toContain('Custom preview'); - }); + })); - it('should be able to constrain the position of a custom preview', async () => { + it('should be able to constrain the position of a custom preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.componentInstance.boundarySelector = '.cdk-drop-list'; fixture.detectChanges(); @@ -1832,7 +1831,7 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; startDraggingViaMouse(fixture, item, listRect.right + 50, listRect.bottom + 50); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', listRect.right + 50, listRect.bottom + 50); fixture.detectChanges(); @@ -1840,9 +1839,9 @@ export function defineCommonDropListTests(config: { expect(Math.floor(previewRect.bottom)).toBe(Math.floor(listRect.bottom)); expect(Math.floor(previewRect.right)).toBe(Math.floor(listRect.right)); - }); + })); - it('should be able to constrain the preview position with a custom function', async () => { + it('should be able to constrain the preview position with a custom function', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); const spy = jasmine.createSpy('constrain position spy').and.returnValue({ x: 50, @@ -1858,7 +1857,7 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; startDraggingViaMouse(fixture, item, 200, 200); - await fixture.whenStable(); + flush(); dispatchMouseEvent(document, 'mousemove', 200, 200); fixture.detectChanges(); @@ -1872,9 +1871,9 @@ export function defineCommonDropListTests(config: { ); expect(Math.floor(previewRect.top)).toBe(50); expect(Math.floor(previewRect.left)).toBe(50); - }); + })); - it('should revert the element back to its parent after dragging with a custom preview has stopped', async () => { + it('should revert the element back to its parent after dragging with a custom preview has stopped', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); @@ -1887,15 +1886,15 @@ export function defineCommonDropListTests(config: { // The coordinates don't matter. dragElementViaMouse(fixture, item, 10, 10); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(dragContainer.contains(item)) .withContext('Expected item to be returned to container.') .toBe(true); - }); + })); - it('should position custom previews next to the pointer', async () => { + it('should position custom previews next to the pointer', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -1905,9 +1904,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.transform).toBe('translate3d(50px, 50px, 0px)'); - }); + })); - it('should keep the preview next to the trigger if the page was scrolled', async () => { + it('should keep the preview next to the trigger if the page was scrolled', fakeAsync(() => { const extractTransform = (element: HTMLElement) => { const match = element.style.transform.match(/translate3d\(\d+px, (\d+)px, \d+px\)/); return match ? parseInt(match[1]) : 0; @@ -1943,9 +1942,9 @@ export function defineCommonDropListTests(config: { expect(extractTransform(preview)).toBeGreaterThan(1000); cleanup(); - }); + })); - it('should lock position inside a drop container along the x axis', async () => { + it('should lock position inside a drop container along the x axis', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); @@ -1963,9 +1962,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.transform).toBe('translate3d(100px, 50px, 0px)'); - }); + })); - it('should lock position inside a drop container along the y axis', async () => { + it('should lock position inside a drop container along the y axis', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); @@ -1983,9 +1982,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should inherit the position locking from the drop container', async () => { + it('should inherit the position locking from the drop container', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.detectChanges(); @@ -2002,9 +2001,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.transform).toBe('translate3d(100px, 50px, 0px)'); - }); + })); - it('should be able to set a class on a custom preview', async () => { + it('should be able to set a class on a custom preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.componentInstance.previewClass = 'custom-class'; fixture.detectChanges(); @@ -2015,9 +2014,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.classList).toContain('custom-preview'); expect(preview.classList).toContain('custom-class'); - }); + })); - it('should be able to apply the size of the dragged element to a custom preview', async () => { + it('should be able to apply the size of the dragged element to a custom preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.componentInstance.matchPreviewSize = true; fixture.detectChanges(); @@ -2031,9 +2030,9 @@ export function defineCommonDropListTests(config: { expect(preview).toBeTruthy(); expect(preview.style.width).toBe(`${itemRect.width}px`); expect(preview.style.height).toBe(`${itemRect.height}px`); - }); + })); - it('should preserve the pickup position if the custom preview inherits the size of the dragged element', async () => { + it('should preserve the pickup position if the custom preview inherits the size of the dragged element', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPreview); fixture.componentInstance.matchPreviewSize = true; fixture.detectChanges(); @@ -2044,9 +2043,9 @@ export function defineCommonDropListTests(config: { const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview.style.transform).toBe('translate3d(8px, 33px, 0px)'); - }); + })); - it('should not have the size of the inserted preview affect the size applied via matchSize', async () => { + it('should not have the size of the inserted preview affect the size applied via matchSize', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalFlexDropZoneWithMatchSizePreview); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2060,35 +2059,39 @@ export function defineCommonDropListTests(config: { expect(Math.floor(previewRect.width)).toBe(Math.floor(itemRect.width)); expect(Math.floor(previewRect.height)).toBe(Math.floor(itemRect.height)); - }); + })); - it('should not throw when custom preview only has text', async () => { + it('should not throw when custom preview only has text', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomTextOnlyPreview); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; - startDraggingViaMouse(fixture, item); + expect(() => { + startDraggingViaMouse(fixture, item); + }).not.toThrow(); const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview).toBeTruthy(); expect(preview.textContent!.trim()).toContain('Hello One'); - }); + })); - it('should handle custom preview with multiple root nodes', async () => { + it('should handle custom preview with multiple root nodes', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomMultiNodePreview); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; - startDraggingViaMouse(fixture, item); + expect(() => { + startDraggingViaMouse(fixture, item); + }).not.toThrow(); const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; expect(preview).toBeTruthy(); expect(preview.textContent!.trim()).toContain('HelloOne'); - }); + })); - it('should be able to customize the placeholder', async () => { + it('should be able to customize the placeholder', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPlaceholder); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2100,12 +2103,12 @@ export function defineCommonDropListTests(config: { expect(placeholder).toBeTruthy(); expect(placeholder.classList).toContain('custom-placeholder'); expect(placeholder.textContent!.trim()).toContain('Custom placeholder'); - }); + })); - it('should handle the custom placeholder being removed', async () => { + it('should handle the custom placeholder being removed', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPlaceholder); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2120,9 +2123,9 @@ export function defineCommonDropListTests(config: { expect(placeholder).toBeTruthy(); expect(placeholder.classList).not.toContain('custom-placeholder'); expect(placeholder.textContent!.trim()).not.toContain('Custom placeholder'); - }); + })); - it('should measure the custom placeholder after the first change detection', async () => { + it('should measure the custom placeholder after the first change detection', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomPlaceholder); fixture.componentInstance.extraPlaceholderClass = 'tall-placeholder'; fixture.detectChanges(); @@ -2139,27 +2142,29 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; expect(event.currentIndex).toBe(2); - }); + })); - it('should not throw when custom placeholder only has text', async () => { + it('should not throw when custom placeholder only has text', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomTextOnlyPlaceholder); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; - startDraggingViaMouse(fixture, item); + expect(() => { + startDraggingViaMouse(fixture, item); + }).not.toThrow(); const placeholder = document.querySelector('.cdk-drag-placeholder')! as HTMLElement; expect(placeholder).toBeTruthy(); expect(placeholder.textContent!.trim()).toContain('Hello One'); - }); + })); - it('should handle custom placeholder with multiple root nodes', () => { + it('should handle custom placeholder with multiple root nodes', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithCustomMultiNodePlaceholder); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2172,9 +2177,9 @@ export function defineCommonDropListTests(config: { expect(placeholder).toBeTruthy(); expect(placeholder.textContent!.trim()).toContain('HelloOne'); - }); + })); - it('should not move the item if the list is disabled', async () => { + it('should not move the item if the list is disabled', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -2202,7 +2207,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.right + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); @@ -2213,9 +2218,9 @@ export function defineCommonDropListTests(config: { 'Two', 'Three', ]); - }); + })); - it('should not throw if the `touches` array is empty', async () => { + it('should not throw if the `touches` array is empty', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2236,9 +2241,9 @@ export function defineCommonDropListTests(config: { dispatchEvent(document, endEvent); fixture.detectChanges(); }).not.toThrow(); - }); + })); - it('should not move the item if the group is disabled', async () => { + it('should not move the item if the group is disabled', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesViaGroupDirective); fixture.detectChanges(); const dragItems = fixture.componentInstance.groupedDragItems[0]; @@ -2263,7 +2268,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.right + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); @@ -2274,9 +2279,9 @@ export function defineCommonDropListTests(config: { 'Two', 'Three', ]); - }); + })); - it('should not sort an item if sorting the list is disabled', async () => { + it('should not sort an item if sorting the list is disabled', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2311,7 +2316,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup', targetX, targetY); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -2338,9 +2343,8 @@ export function defineCommonDropListTests(config: { 'Two', 'Three', ]); - }); + })); - // TODO(crisbeto): this one is using `fakeAsync` because we throw an error from a subscription. it('should not throw if an item is removed after dragging has started', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2364,7 +2368,7 @@ export function defineCommonDropListTests(config: { }).not.toThrow(); })); - it('should not be able to start a drag sequence while another one is still active', async () => { + it('should not be able to start a drag sequence while another one is still active', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const [item, otherItem] = fixture.componentInstance.dragItems.toArray(); @@ -2380,9 +2384,9 @@ export function defineCommonDropListTests(config: { expect(document.querySelectorAll('.cdk-drag-dragging').length) .withContext('Expected only one item to continue to be dragged.') .toBe(1); - }); + })); - it('should should be able to disable auto-scrolling', async () => { + it('should should be able to disable auto-scrolling', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2402,12 +2406,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollTop).toBe(0); - }); + })); - it('should auto-scroll down if the user holds their pointer at bottom edge', async () => { + it('should auto-scroll down if the user holds their pointer at bottom edge', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2424,12 +2428,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollTop).toBeGreaterThan(0); - }); + })); - it('should auto-scroll up if the user holds their pointer at top edge', async () => { + it('should auto-scroll up if the user holds their pointer at top edge', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2440,12 +2444,12 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width / 2, listRect.top); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollTop).toBeLessThan(initialScrollDistance); - }); + })); - it('should auto-scroll right if the user holds their pointer at right edge in ltr', async () => { + it('should auto-scroll right if the user holds their pointer at right edge in ltr', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableHorizontalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2462,12 +2466,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height / 2, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollLeft).toBeGreaterThan(0); - }); + })); - it('should auto-scroll left if the user holds their pointer at left edge in ltr', async () => { + it('should auto-scroll left if the user holds their pointer at left edge in ltr', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableHorizontalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2478,12 +2482,12 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', listRect.left, listRect.top + listRect.height / 2); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollLeft).toBeLessThan(initialScrollDistance); - }); + })); - it('should auto-scroll right if the user holds their pointer at right edge in rtl', async () => { + it('should auto-scroll right if the user holds their pointer at right edge in rtl', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableHorizontalDropZone, { providers: [provideFakeDirectionality('rtl')], }); @@ -2502,12 +2506,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height / 2, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollLeft).toBeGreaterThan(initialScrollDistance); - }); + })); - it('should auto-scroll left if the user holds their pointer at left edge in rtl', async () => { + it('should auto-scroll left if the user holds their pointer at left edge in rtl', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableHorizontalDropZone, { providers: [provideFakeDirectionality('rtl')], }); @@ -2522,12 +2526,12 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', listRect.left, listRect.top + listRect.height / 2); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollLeft).toBeLessThan(0); - }); + })); - it('should be able to start auto scrolling with a drag boundary', async () => { + it('should be able to start auto scrolling with a drag boundary', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableHorizontalDropZone); fixture.componentInstance.boundarySelector = '.drop-list'; fixture.detectChanges(); @@ -2545,12 +2549,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height / 2, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollLeft).toBeGreaterThan(0); - }); + })); - it('should stop scrolling if the user moves their pointer away', async () => { + it('should stop scrolling if the user moves their pointer away', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2567,7 +2571,7 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); const previousScrollTop = list.scrollTop; expect(previousScrollTop).toBeGreaterThan(0); @@ -2580,12 +2584,12 @@ export function defineCommonDropListTests(config: { listRect.bottom + listRect.height / 2, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(list.scrollTop).toBe(previousScrollTop); - }); + })); - it('should stop scrolling if the user stops dragging', async () => { + it('should stop scrolling if the user stops dragging', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2602,21 +2606,21 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); const previousScrollTop = list.scrollTop; expect(previousScrollTop).toBeGreaterThan(0); dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await wait(100); - await fixture.whenStable(); + tickAnimationFrames(20); + flush(); fixture.detectChanges(); expect(list.scrollTop).toBe(previousScrollTop); - }); + })); - it('should auto-scroll viewport down if the pointer is close to bottom edge', async () => { + it('should auto-scroll viewport down if the pointer is close to bottom edge', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2630,14 +2634,14 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', viewportSize.width / 2, viewportSize.height); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().top).toBeGreaterThan(0); cleanup(); - }); + })); - it('should auto-scroll viewport up if the pointer is close to top edge', async () => { + it('should auto-scroll viewport up if the pointer is close to top edge', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2653,14 +2657,14 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', viewportSize.width / 2, 0); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().top).toBeLessThan(initialScrollDistance); cleanup(); - }); + })); - it('should auto-scroll viewport right if the pointer is near right edge', async () => { + it('should auto-scroll viewport right if the pointer is near right edge', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2674,14 +2678,14 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', viewportSize.width, viewportSize.height / 2); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().left).toBeGreaterThan(0); cleanup(); - }); + })); - it('should auto-scroll viewport left if the pointer is close to left edge', async () => { + it('should auto-scroll viewport left if the pointer is close to left edge', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2697,14 +2701,14 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', 0, viewportSize.height / 2); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().left).toBeLessThan(initialScrollDistance); cleanup(); - }); + })); - it('should auto-scroll the list, not the viewport, when the pointer is over the edge of both the list and the viewport', async () => { + it('should auto-scroll the list, not the viewport, when the pointer is over the edge of both the list and the viewport', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); @@ -2733,15 +2737,15 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width / 2, 0); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().top).toBe(initialScrollDistance); expect(list.scrollTop).toBeLessThan(50); cleanup(); - }); + })); - it('should auto-scroll the viewport, when the pointer is over the edge of both the list and the viewport, if the list cannot be scrolled in that direction', async () => { + it('should auto-scroll the viewport, when the pointer is over the edge of both the list and the viewport, if the list cannot be scrolled in that direction', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); @@ -2770,15 +2774,15 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width / 2, 0); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(viewportRuler.getViewportScrollPosition().top).toBeLessThan(initialScrollDistance); expect(list.scrollTop).toBe(0); cleanup(); - }); + })); - it('should be able to auto-scroll a parent container', async () => { + it('should be able to auto-scroll a parent container', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableParentContainer); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; @@ -2795,12 +2799,12 @@ export function defineCommonDropListTests(config: { containerRect.top + containerRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(20); expect(container.scrollTop).toBeGreaterThan(0); - }); + })); - it('should be able to configure the auto-scroll speed', async () => { + it('should be able to configure the auto-scroll speed', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); fixture.componentInstance.dropInstance.autoScrollStep = 20; @@ -2818,12 +2822,12 @@ export function defineCommonDropListTests(config: { listRect.top + listRect.height, ); fixture.detectChanges(); - await wait(100); + tickAnimationFrames(10); expect(list.scrollTop).toBeGreaterThan(100); - }); + })); - it('should pick up descendants inside of containers', async () => { + it('should pick up descendants inside of containers', fakeAsync(() => { const fixture = createComponent(DraggableInDropZoneWithContainer); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -2836,7 +2840,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -2856,9 +2860,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should not pick up items from descendant drop lists', async () => { + it('should not pick up items from descendant drop lists', fakeAsync(() => { const fixture = createComponent(NestedDropZones); fixture.detectChanges(); const {dragItems, innerList, outerList} = fixture.componentInstance; @@ -2882,13 +2886,13 @@ export function defineCommonDropListTests(config: { .withContext('Expected inner list to be dragging.') .toContain(draggingClass); expect(outerClasses).not.toContain(draggingClass, 'Expected outer list to not be dragging.'); - }); + })); - it('should be able to re-enable a disabled drop list', async () => { + it('should be able to re-enable a disabled drop list', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; - const tryDrag = async () => { + const tryDrag = () => { const firstItem = dragItems.first; const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse( @@ -2897,7 +2901,7 @@ export function defineCommonDropListTests(config: { thirdItemRect.left + 1, thirdItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); }; @@ -2910,7 +2914,7 @@ export function defineCommonDropListTests(config: { fixture.componentInstance.dropDisabled.set(true); fixture.detectChanges(); - await tryDrag(); + tryDrag(); expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())).toEqual([ 'Zero', @@ -2921,7 +2925,7 @@ export function defineCommonDropListTests(config: { fixture.componentInstance.dropDisabled.set(false); fixture.detectChanges(); - await tryDrag(); + tryDrag(); expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())).toEqual([ 'One', @@ -2929,9 +2933,9 @@ export function defineCommonDropListTests(config: { 'Zero', 'Three', ]); - }); + })); - it('should be able to configure the drop input defaults through a provider', async () => { + it('should be able to configure the drop input defaults through a provider', fakeAsync(() => { const config: DragDropConfig = { draggingDisabled: true, sortingDisabled: true, @@ -2955,9 +2959,9 @@ export function defineCommonDropListTests(config: { expect(list.autoScrollDisabled).toBe(true); expect(list.orientation).toBe('horizontal'); expect(list.lockAxis).toBe('y'); - }); + })); - it('should disable scroll snapping while the user is dragging', async () => { + it('should disable scroll snapping while the user is dragging', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -2976,13 +2980,13 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(styles.scrollSnapType || styles.msScrollSnapType).toBeFalsy(); - }); + })); - it('should restore the previous inline scroll snap value', async () => { + it('should restore the previous inline scroll snap value', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -3002,13 +3006,13 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(styles.scrollSnapType || styles.msScrollSnapType).toBe('block'); - }); + })); - it('should be able to start dragging again if the dragged item is destroyed', async () => { + it('should be able to start dragging again if the dragged item is destroyed', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -3039,12 +3043,12 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should make the placeholder available in the start event', async () => { + it('should make the placeholder available in the start event', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; @@ -3056,9 +3060,9 @@ export function defineCommonDropListTests(config: { startDraggingViaMouse(fixture, item); expect(placeholder).toBeTruthy(); - }); + })); - it('should not move item into position not allowed by the sort predicate', async () => { + it('should not move item into position not allowed by the sort predicate', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -3089,7 +3093,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3106,9 +3110,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should not call the sort predicate for the same index', () => { + it('should not call the sort predicate for the same index', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); const spy = jasmine.createSpy('sort predicate spy').and.returnValue(true); @@ -3122,15 +3126,16 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(spy).not.toHaveBeenCalled(); - }); + })); - it('should throw if drop list is attached to an ng-container', () => { + it('should throw if drop list is attached to an ng-container', fakeAsync(() => { expect(() => { createComponent(DropListOnNgContainer).detectChanges(); + flush(); }).toThrowError(/^cdkDropList must be attached to an element node/); - }); + })); - it('should sort correctly if the node has been offset', async () => { + it('should sort correctly if the node has been offset', fakeAsync(() => { const documentElement = document.documentElement!; const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -3138,7 +3143,7 @@ export function defineCommonDropListTests(config: { documentElement.style.position = 'absolute'; documentElement.style.top = '100px'; - await assertStartToEndSorting( + assertStartToEndSorting( 'vertical', fixture, config.getSortedSiblings, @@ -3147,11 +3152,11 @@ export function defineCommonDropListTests(config: { documentElement.style.position = ''; documentElement.style.top = ''; - }); + })); }); describe('in a connected drop container', () => { - it('should dispatch the `dropped` event when an item has been dropped into a new container', async () => { + it('should dispatch the `dropped` event when an item has been dropped into a new container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3165,7 +3170,7 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -3183,9 +3188,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should be able to move the element over a new container and return it', async () => { + it('should be able to move the element over a new container and return it', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3222,9 +3227,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should be able to move the element over a new container and return it to the initial one, even if it no longer matches the enterPredicate', async () => { + it('should be able to move the element over a new container and return it to the initial one, even if it no longer matches the enterPredicate', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3264,9 +3269,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should transfer the DOM element from one drop zone to another', async () => { + it('should transfer the DOM element from one drop zone to another', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3278,7 +3283,7 @@ export function defineCommonDropListTests(config: { // Use coordinates of [1] item corresponding to [2] item // after dragged item is removed from first container dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3295,9 +3300,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should not be able to transfer an item into a container that is not in `connectedTo`', async () => { + it('should not be able to transfer an item into a container that is not in `connectedTo`', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.componentInstance.todoConnectedTo.set([]); fixture.componentInstance.doneConnectedTo.set([]); @@ -3310,7 +3315,7 @@ export function defineCommonDropListTests(config: { const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3327,9 +3332,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should not be able to transfer an item that does not match the `enterPredicate`', async () => { + it('should not be able to transfer an item that does not match the `enterPredicate`', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3342,7 +3347,7 @@ export function defineCommonDropListTests(config: { const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3359,9 +3364,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should call the `enterPredicate` with the item and the container it is entering', async () => { + it('should call the `enterPredicate` with the item and the container it is entering', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3380,13 +3385,13 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(spy).toHaveBeenCalledWith(dragItem, dropInstances[1]); - }); + })); - it('should be able to start dragging after an item has been transferred', async () => { + it('should be able to start dragging after an item has been transferred', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3396,21 +3401,21 @@ export function defineCommonDropListTests(config: { const targetRect = dropZone.getBoundingClientRect(); // Drag the element into the drop zone and move it to the top. - for (const offset of [1, -1]) { + [1, -1].forEach(offset => { dragElementViaMouse(fixture, element, targetRect.left + offset, targetRect.top + offset); - await fixture.whenStable(); + flush(); fixture.detectChanges(); - } + }); - await assertStartToEndSorting( + assertStartToEndSorting( 'vertical', fixture, config.getSortedSiblings, Array.from(dropZone.querySelectorAll('.cdk-drag')), ); - }); + })); - it('should be able to return the last item inside its initial container', async () => { + it('should be able to return the last item inside its initial container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); // Make sure there's only one item in the first list. @@ -3451,9 +3456,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should update drop zone after element has entered', async () => { + it('should update drop zone after element has entered', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); // Make sure there's only one item in the first list. @@ -3493,7 +3498,7 @@ export function defineCommonDropListTests(config: { // Drop and verify item drop positon and coontainer dispatchMouseEvent(document, 'mouseup', targetRect.left + 1, targetRect.bottom - 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3510,9 +3515,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should enter as first child if entering from top', async () => { + it('should enter as first child if entering from top', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); // Make sure there's only one item in the first list. @@ -3546,9 +3551,9 @@ export function defineCommonDropListTests(config: { .toBe(true); dispatchMouseEvent(document, 'mouseup'); - }); + })); - it('should not throw when entering from the top with an intermediate sibling present', async () => { + it('should not throw when entering from the top with an intermediate sibling present', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesWithIntermediateSibling); // Make sure there's only one item in the first list. @@ -3563,12 +3568,14 @@ export function defineCommonDropListTests(config: { dropZones[1].style.paddingTop = '10px'; const targetRect = dropZones[1].getBoundingClientRect(); - dragElementViaMouse(fixture, item.element.nativeElement, targetRect.left, targetRect.top); - await fixture.whenStable(); - fixture.detectChanges(); - }); + expect(() => { + dragElementViaMouse(fixture, item.element.nativeElement, targetRect.left, targetRect.top); + flush(); + fixture.detectChanges(); + }).not.toThrow(); + })); - it('should assign a default id on each drop zone', async () => { + it('should assign a default id on each drop zone', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3577,9 +3584,9 @@ export function defineCommonDropListTests(config: { return !!dropZone.id && !!dropZone.element.nativeElement.getAttribute('id'); }), ).toBe(true); - }); + })); - it('should be able to connect two drop zones by id', async () => { + it('should be able to connect two drop zones by id', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3595,7 +3602,7 @@ export function defineCommonDropListTests(config: { const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3612,9 +3619,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should be able to connect two drop zones using the drop list group', async () => { + it('should be able to connect two drop zones using the drop list group', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesViaGroupDirective); fixture.detectChanges(); @@ -3624,7 +3631,7 @@ export function defineCommonDropListTests(config: { const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3641,9 +3648,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should be able to pass a single id to `connectedTo`', async () => { + it('should be able to pass a single id to `connectedTo`', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3658,7 +3665,7 @@ export function defineCommonDropListTests(config: { const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3675,9 +3682,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should return DOM element to its initial container after it is dropped, in a container with one draggable item', async () => { + it('should return DOM element to its initial container after it is dropped, in a container with one draggable item', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesWithSingleItems); fixture.detectChanges(); @@ -3701,7 +3708,7 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -3726,9 +3733,9 @@ export function defineCommonDropListTests(config: { expect(item.dropContainer) .withContext('Expected CdkDrag to be returned to first container in memory') .toBe(fixture.componentInstance.dropInstances.first); - }); + })); - it('should be able to return an element to its initial container in the same sequence, even if it is not connected to the current container', async () => { + it('should be able to return an element to its initial container in the same sequence, even if it is not connected to the current container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3774,17 +3781,17 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should not add child drop lists to the same group as their parents', async () => { + it('should not add child drop lists to the same group as their parents', fakeAsync(() => { const fixture = createComponent(NestedDropListGroups); const component = fixture.componentInstance; fixture.detectChanges(); expect(Array.from(component.group._items)).toEqual([component.listOne, component.listTwo]); - }); + })); - it('should not be able to drop an element into a container that is under another element', async () => { + it('should not be able to drop an element into a container that is under another element', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3807,7 +3814,7 @@ export function defineCommonDropListTests(config: { }); dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -3824,9 +3831,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should set a class when a container can receive an item', async () => { + it('should set a class when a container can receive an item', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3848,9 +3855,9 @@ export function defineCommonDropListTests(config: { expect(dropZones[1].classList) .withContext('Expected target container to have the receiving class.') .toContain('cdk-drop-list-receiving'); - }); + })); - it('should toggle the `receiving` class when the item enters a new list', async () => { + it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -3884,9 +3891,9 @@ export function defineCommonDropListTests(config: { expect(dropZones[1].classList) .not.withContext('Expected new container not to have the receiving class after exiting.') .toContain('cdk-drop-list-receiving'); - }); + })); - it('should not set the receiving class if the item does not match the enter predicate', async () => { + it('should not set the receiving class if the item does not match the enter predicate', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); fixture.componentInstance.dropInstances.toArray()[1].enterPredicate = () => false; @@ -3904,9 +3911,9 @@ export function defineCommonDropListTests(config: { expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving'))) .withContext('Expected neither of the containers to have the class.') .toBe(true); - }); + })); - it('should set the receiving class on the source container, even if the enter predicate does not match', async () => { + it('should set the receiving class on the source container, even if the enter predicate does not match', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); fixture.componentInstance.dropInstances.toArray()[0].enterPredicate = () => false; @@ -3941,9 +3948,9 @@ export function defineCommonDropListTests(config: { 'cdk-drop-list-receiving', 'Expected new container not to have the receiving class after exiting.', ); - }); + })); - it('should set the receiving class when the list is wrapped in an OnPush component', async () => { + it('should set the receiving class when the list is wrapped in an OnPush component', fakeAsync(() => { const fixture = createComponent(ConnectedDropListsInOnPush); fixture.detectChanges(); @@ -3966,9 +3973,9 @@ export function defineCommonDropListTests(config: { expect(dropZones[1].classList) .withContext('Expected target container to have the receiving class.') .toContain('cdk-drop-list-receiving'); - }); + })); - it('should be able to move the item over an intermediate container before dropping it into the final one', async () => { + it('should be able to move the item over an intermediate container before dropping it into the final one', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4017,7 +4024,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -4036,9 +4043,9 @@ export function defineCommonDropListTests(config: { event: jasmine.anything(), }), ); - }); + })); - it('should not be able to move an item into a drop container that the initial container is not connected to by passing it over an intermediate one that is', async () => { + it('should not be able to move an item into a drop container that the initial container is not connected to by passing it over an intermediate one that is', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4087,7 +4094,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; @@ -4103,9 +4110,9 @@ export function defineCommonDropListTests(config: { isPointerOverContainer: false, }), ); - }); + })); - it('should return the item to its initial position, if sorting in the source container was disabled', async () => { + it('should return the item to its initial position, if sorting in the source container was disabled', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4159,9 +4166,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should enter an item into the correct index when returning to the initial container, if sorting is enabled', async () => { + it('should enter an item into the correct index when returning to the initial container, if sorting is enabled', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4206,9 +4213,9 @@ export function defineCommonDropListTests(config: { expect(config.getSortedSiblings(placeholder, 'top').indexOf(placeholder)) .withContext('Expected placeholder to be at the index at which it entered.') .toBe(2); - }); + })); - it('should return the last item to initial position when dragging back into a container with disabled sorting', async () => { + it('should return the last item to initial position when dragging back into a container with disabled sorting', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4263,9 +4270,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled(); - }); + })); - it('should toggle a class when dragging an item inside a wrapper component component with OnPush change detection', async () => { + it('should toggle a class when dragging an item inside a wrapper component component with OnPush change detection', fakeAsync(() => { const fixture = createComponent(ConnectedWrappedDropZones); fixture.detectChanges(); @@ -4300,9 +4307,9 @@ export function defineCommonDropListTests(config: { expect(targetZone.classList) .withContext('Expected target to have dragging class once item has been moved over.') .toContain('cdk-drop-list-dragging'); - }); + })); - it('should dispatch an event when an item enters a new container', async () => { + it('should dispatch an event when an item enters a new container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4325,9 +4332,9 @@ export function defineCommonDropListTests(config: { expect(containerEnterEvent).toEqual(expectedEvent); expect(itemEnterEvent).toEqual(expectedEvent); - }); + })); - it('should not throw if dragging was interrupted as a result of the entered event', async () => { + it('should not throw if dragging was interrupted as a result of the entered event', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4347,11 +4354,12 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); + flush(); fixture.detectChanges(); }).not.toThrow(); - }); + })); - it('should be able to drop into a new container after scrolling into view', async () => { + it('should be able to drop into a new container after scrolling into view', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4360,7 +4368,7 @@ export function defineCommonDropListTests(config: { scrollTo(0, 0); dispatchFakeEvent(document, 'scroll'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const groups = fixture.componentInstance.groupedDragItems; @@ -4375,7 +4383,7 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1); dispatchMouseEvent(document, 'mouseup', targetRect.left + 1, targetRect.top + 1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -4395,9 +4403,9 @@ export function defineCommonDropListTests(config: { }); cleanup(); - }); + })); - it('should be able to drop into a new container inside the Shadow DOM', async () => { + it('should be able to drop into a new container inside the Shadow DOM', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -4418,7 +4426,7 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -4436,9 +4444,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should be able to drop into a new container inside the Shadow DOM and ngIf', async () => { + it('should be able to drop into a new container inside the Shadow DOM and ngIf', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -4457,7 +4465,7 @@ export function defineCommonDropListTests(config: { targetRect.left + 1, targetRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -4475,9 +4483,9 @@ export function defineCommonDropListTests(config: { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should prevent selection at the shadow root level', async () => { + it('should prevent selection at the shadow root level', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -4500,14 +4508,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const afterDropSelectStart = dispatchFakeEvent(shadowRoot, 'selectstart'); fixture.detectChanges(); expect(afterDropSelectStart.defaultPrevented).toBe(false); - }); + })); - it('should not throw if its next sibling is removed while dragging', async () => { + it('should not throw if its next sibling is removed while dragging', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesWithSingleItems); fixture.detectChanges(); @@ -4520,20 +4528,23 @@ export function defineCommonDropListTests(config: { // Manually insert an element after the node to simulate an external package. nextSibling.parentNode!.insertBefore(extraSibling, nextSibling); + dragElementViaMouse( + fixture, + item.element.nativeElement, + targetRect.left + 1, + targetRect.top + 1, + ); + + // Remove the extra node after the element was dropped, but before the animation is over. + extraSibling.remove(); + expect(() => { - dragElementViaMouse( - fixture, - item.element.nativeElement, - targetRect.left + 1, - targetRect.top + 1, - ); - // Remove the extra node after the element was dropped, but before the animation is over. - extraSibling.remove(); + flush(); fixture.detectChanges(); }).not.toThrow(); - }); + })); - it('should warn when the connected container ID does not exist', async () => { + it('should warn when the connected container ID does not exist', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4545,15 +4556,15 @@ export function defineCommonDropListTests(config: { spyOn(console, 'warn'); dragElementViaMouse(fixture, element, 0, 0); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(console.warn).toHaveBeenCalledWith( `CdkDropList could not find connected drop ` + `list with id "does-not-exist"`, ); - }); + })); - it('should not be able to start a drag sequence while a connected container is active', async () => { + it('should not be able to start a drag sequence while a connected container is active', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); const item = fixture.componentInstance.groupedDragItems[0][0]; @@ -4570,9 +4581,9 @@ export function defineCommonDropListTests(config: { expect(document.querySelectorAll('.cdk-drag-dragging').length) .withContext('Expected only one item to continue to be dragged.') .toBe(1); - }); + })); - it('should insert the preview inside the shadow root by default', async () => { + it('should insert the preview inside the shadow root by default', fakeAsync(() => { // This test is only relevant for Shadow DOM-supporting browsers. if (!_supportsShadowDom()) { return; @@ -4590,11 +4601,11 @@ export function defineCommonDropListTests(config: { // `querySelector` doesn't descend into the shadow DOM so we can assert that the preview // isn't at its default location by searching for it at the `document` level. expect(document.querySelector('.cdk-drag-preview')).toBeFalsy(); - }); + })); }); describe('with nested drags', () => { - it('should not move draggable container when dragging child (multitouch)', async () => { + it('should not move draggable container when dragging child (multitouch)', fakeAsync(() => { const fixture = createComponent(NestedDragsComponent, {dragDistance: 10}); fixture.detectChanges(); @@ -4644,9 +4655,9 @@ export function defineCommonDropListTests(config: { expect(fixture.componentInstance.containerDragReleasedSpy).toHaveBeenCalledTimes( containerDragReleasedCount, ); - }); + })); - it('should stop event propagation when dragging a nested item', () => { + it('should stop event propagation when dragging a nested item', fakeAsync(() => { const fixture = createComponent(NestedDragsComponent); fixture.detectChanges(); const dragElement = fixture.componentInstance.item.nativeElement; @@ -4658,9 +4669,9 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(event.stopPropagation).toHaveBeenCalled(); - }); + })); - it('should stop event propagation when dragging item nested via ng-template', () => { + it('should stop event propagation when dragging item nested via ng-template', fakeAsync(() => { const fixture = createComponent(NestedDragsThroughTemplate); fixture.detectChanges(); const dragElement = fixture.componentInstance.item.nativeElement; @@ -4672,11 +4683,11 @@ export function defineCommonDropListTests(config: { fixture.detectChanges(); expect(event.stopPropagation).toHaveBeenCalled(); - }); + })); }); describe('with an alternate element container', () => { - it('should move the placeholder into the alternate container of an empty list', () => { + it('should move the placeholder into the alternate container of an empty list', fakeAsync(() => { const fixture = createComponent(ConnectedDropZonesWithAlternateContainer); fixture.detectChanges(); @@ -4701,9 +4712,8 @@ export function defineCommonDropListTests(config: { expect(placeholder.parentNode) .withContext('Expected placeholder to be inside second container.') .toBe(targetContainer); - }); + })); - // TODO(crisbeto): this one is using `fakeAsync` because we throw an error from a subscription. it('should throw if the items are not inside of the alternate container', fakeAsync(() => { const fixture = createComponent(DraggableWithInvalidAlternateContainer); fixture.detectChanges(); @@ -4717,7 +4727,6 @@ export function defineCommonDropListTests(config: { ); })); - // TODO(crisbeto): this one is using `fakeAsync` because we throw an error from a subscription. it('should throw if the alternate container cannot be found', fakeAsync(() => { const fixture = createComponent(DraggableWithMissingAlternateContainer); fixture.detectChanges(); @@ -4741,7 +4750,7 @@ export function defineCommonDropListTests(config: { return container.querySelector('.cdk-drag-placeholder'); } - it('should create and manage the anchor element when the item is moved into a new container', async () => { + it('should create and manage the anchor element when the item is moved into a new container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.componentInstance.hasAnchor.set(true); fixture.detectChanges(); @@ -4774,14 +4783,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup', x, y); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(getAnchor(fixture.nativeElement)).toBeFalsy(); expect(getPlaceholder(fixture.nativeElement)).toBeFalsy(); - }); + })); - it('should remove the anchor when the item is returned to the initial container', async () => { + it('should remove the anchor when the item is returned to the initial container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.componentInstance.hasAnchor.set(true); fixture.detectChanges(); @@ -4819,14 +4828,14 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup', sourceRect.left + 1, sourceRect.top + 1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(getAnchor(fixture.nativeElement)).toBeFalsy(); expect(getPlaceholder(fixture.nativeElement)).toBeFalsy(); - }); + })); - it('should keep the anchor inside the initial container as the item is moved between containers', async () => { + it('should keep the anchor inside the initial container as the item is moved between containers', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -4884,16 +4893,16 @@ export function defineCommonDropListTests(config: { dispatchMouseEvent(document, 'mouseup', thirdRect.left + 1, thirdRect.top + 1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(getAnchor(fixture.nativeElement)).toBeFalsy(); expect(getPlaceholder(fixture.nativeElement)).toBeFalsy(); - }); + })); }); } -export async function assertStartToEndSorting( +export function assertStartToEndSorting( listOrientation: 'vertical' | 'horizontal', fixture: ComponentFixture, getSortedSiblings: SortedSiblingsFunction, @@ -4930,10 +4939,10 @@ export async function assertStartToEndSorting( dispatchMouseEvent(document, 'mouseup'); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); } -export async function assertEndToStartSorting( +export function assertEndToStartSorting( listOrientation: 'vertical' | 'horizontal', fixture: ComponentFixture, getSortedSiblings: SortedSiblingsFunction, @@ -4969,7 +4978,7 @@ export async function assertEndToStartSorting( dispatchMouseEvent(document, 'mouseup'); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); } /** @@ -4999,7 +5008,7 @@ export function getHorizontalFixtures(listOrientation: Exclude @for (item of items; track item) { @@ -5017,11 +5026,9 @@ export function getHorizontalFixtures(listOrientation: Exclude; @ViewChild(CdkDropList) dropInstance!: CdkDropList; @@ -5036,6 +5043,10 @@ export function getHorizontalFixtures(listOrientation: Exclude +
    @for (item of items; track item) {
    {{item}} @@ -5101,11 +5111,8 @@ export function getHorizontalFixtures(listOrientation: Exclude `, imports: [CdkDropList, CdkDrag, CdkDragPreview], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInHorizontalFlexDropZoneWithMatchSizePreview { - readonly _listOrientation = listOrientation; - @ViewChildren(CdkDrag) dragItems!: QueryList; items = ['Zero', 'One', 'Two']; } @@ -5149,7 +5156,6 @@ const DROP_ZONE_FIXTURE_TEMPLATE = ` @Component({ template: DROP_ZONE_FIXTURE_TEMPLATE, imports: [CdkDropList, CdkDrag, NgFor], - changeDetection: ChangeDetectionStrategy.Eager, }) export class DraggableInDropZone implements AfterViewInit { protected _elementRef = inject(ElementRef); @@ -5173,7 +5179,7 @@ export class DraggableInDropZone implements AfterViewInit { startedSpy = jasmine.createSpy('started spy'); previewContainer: PreviewContainer = 'global'; dropDisabled = signal(false); - dropLockAxis = signal(null); + dropLockAxis = signal(undefined); scale = 1; ngAfterViewInit() { @@ -5187,6 +5193,7 @@ export class DraggableInDropZone implements AfterViewInit { @Component({ selector: 'draggable-in-on-push-zone', template: DROP_ZONE_FIXTURE_TEMPLATE, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkDropList, CdkDrag, NgFor], }) class DraggableInOnPushDropZone extends DraggableInDropZone {} @@ -5199,7 +5206,6 @@ class DraggableInOnPushDropZone extends DraggableInDropZone {}
    `, imports: [CdkDropListGroup, DraggableInOnPushDropZone], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropListsInOnPush {} @@ -5216,7 +5222,6 @@ class ConnectedDropListsInOnPush {} } `, imports: [CdkDropList, CdkDrag, NgFor], - changeDetection: ChangeDetectionStrategy.Eager, }) export class DraggableInScrollableVerticalDropZone extends DraggableInDropZone { constructor() { @@ -5245,7 +5250,6 @@ export class DraggableInScrollableVerticalDropZone extends DraggableInDropZone { } `, imports: [CdkDropList, CdkDrag, NgFor, CdkScrollable], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInScrollableParentContainer extends DraggableInDropZone implements AfterViewInit { @ViewChild('scrollContainer') scrollContainer!: ElementRef; @@ -5293,7 +5297,6 @@ class DraggableInScrollableParentContainer extends DraggableInDropZone implement
    `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithContainer extends DraggableInDropZone {} @@ -5324,38 +5327,40 @@ class DraggableInDropZoneWithContainer extends DraggableInDropZone {} `, imports: [CdkDropList, CdkDrag, CdkDragPreview, NgIf], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithCustomPreview { @ViewChild(CdkDropList) dropInstance!: CdkDropList; @ViewChildren(CdkDrag) dragItems!: QueryList; - items: {label: string; lockAxis: DragAxis | null}[] = [ - {label: 'Zero', lockAxis: null}, - {label: 'One', lockAxis: null}, - {label: 'Two', lockAxis: null}, - {label: 'Three', lockAxis: null}, + items: {label: string; lockAxis?: DragAxis}[] = [ + {label: 'Zero'}, + {label: 'One'}, + {label: 'Two'}, + {label: 'Three'}, ]; boundarySelector!: string; renderCustomPreview = true; matchPreviewSize = false; previewClass: string | string[] = []; constrainPosition: ((point: Point) => Point) | undefined; - dropLockAxis = signal(null); + dropLockAxis = signal(undefined); } @Component({ template: `
    @for (item of items; track item) { -
    - {{item}} - Hello {{item}} +
    + {{item}} + Hello {{item}}
    }
    `, imports: [CdkDropList, CdkDrag, CdkDragPreview], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithCustomTextOnlyPreview { @ViewChild(CdkDropList) dropInstance!: CdkDropList; @@ -5378,7 +5383,6 @@ class DraggableInDropZoneWithCustomTextOnlyPreview {
    `, imports: [CdkDropList, CdkDrag, CdkDragPreview], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithCustomMultiNodePreview { @ViewChild(CdkDropList) dropInstance!: CdkDropList; @@ -5398,7 +5402,7 @@ class DraggableInDropZoneWithCustomMultiNodePreview { @if (renderPlaceholder) {
    Custom placeholder
    } @@ -5410,8 +5414,7 @@ class DraggableInDropZoneWithCustomMultiNodePreview { height: ${ITEM_HEIGHT * 3}px; } `, - imports: [CdkDropList, CdkDrag, CdkDragPlaceholder], - changeDetection: ChangeDetectionStrategy.Eager, + imports: [CdkDropList, CdkDrag, CdkDragPlaceholder, NgClass], }) class DraggableInDropZoneWithCustomPlaceholder { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -5433,7 +5436,6 @@ class DraggableInDropZoneWithCustomPlaceholder { `, imports: [CdkDropList, CdkDrag, CdkDragPlaceholder], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithCustomTextOnlyPlaceholder { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -5455,7 +5457,6 @@ class DraggableInDropZoneWithCustomTextOnlyPlaceholder { `, imports: [CdkDropList, CdkDrag, CdkDragPlaceholder], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInDropZoneWithCustomMultiNodePlaceholder { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -5534,7 +5535,6 @@ const CONNECTED_DROP_ZONES_TEMPLATE = ` styles: CONNECTED_DROP_ZONES_STYLES, template: CONNECTED_DROP_ZONES_TEMPLATE, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) export class ConnectedDropZones implements AfterViewInit { @ViewChildren(CdkDrag) rawDragItems!: QueryList; @@ -5570,7 +5570,6 @@ export class ConnectedDropZones implements AfterViewInit { styles: CONNECTED_DROP_ZONES_STYLES, template: `@if (true) {${CONNECTED_DROP_ZONES_TEMPLATE}}`, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropZonesInsideShadowRootWithNgIf extends ConnectedDropZones {} @@ -5612,7 +5611,6 @@ class ConnectedDropZonesInsideShadowRootWithNgIf extends ConnectedDropZones {} `, imports: [CdkDropList, CdkDrag, CdkDropListGroup], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones { groupDisabled = false; @@ -5652,7 +5650,6 @@ class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropZonesWithSingleItems { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -5673,7 +5670,6 @@ class ConnectedDropZonesWithSingleItems { `, imports: [CdkDropList, CdkDropListGroup], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedDropListGroups { @ViewChild('group') group!: CdkDropListGroup; @@ -5686,11 +5682,11 @@ class NestedDropListGroups { `, imports: [CdkDropList], - changeDetection: ChangeDetectionStrategy.Eager, }) class DropListOnNgContainer {} @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkDropList, CdkDrag], template: `
    @@ -5725,6 +5721,7 @@ class DraggableInDropZoneWithoutEvents {
    `, imports: [CdkDropList, CdkDrag], + changeDetection: ChangeDetectionStrategy.OnPush, }) class WrappedDropContainerComponent { @Input() items: string[] | undefined; @@ -5753,7 +5750,6 @@ class WrappedDropContainerComponent { `, imports: [CdkDropListGroup, WrappedDropContainerComponent], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedWrappedDropZones { todo = ['Zero', 'One', 'Two', 'Three']; @@ -5784,7 +5780,6 @@ class ConnectedWrappedDropZones { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithCanvasInDropZone extends DraggableInDropZone implements AfterViewInit { override ngAfterViewInit() { @@ -5826,7 +5821,6 @@ class DraggableWithCanvasInDropZone extends DraggableInDropZone implements After `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithInvalidCanvasInDropZone extends DraggableInDropZone {} @@ -5866,7 +5860,6 @@ class DraggableWithInvalidCanvasInDropZone extends DraggableInDropZone {} `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedDragsComponent { @ViewChild('container') container!: ElementRef; @@ -5921,9 +5914,11 @@ class NestedDragsComponent { `, imports: [CdkDrag, NgTemplateOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) -class NestedDragsThroughTemplate extends NestedDragsComponent {} +class NestedDragsThroughTemplate { + @ViewChild('container') container!: ElementRef; + @ViewChild('item') item!: ElementRef; +} @Component({ styles: ` @@ -5944,7 +5939,6 @@ class NestedDragsThroughTemplate extends NestedDragsComponent {} `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedDropZones { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -5956,7 +5950,6 @@ class NestedDropZones { @Component({ template: `
    `, imports: [CdkDropList], - changeDetection: ChangeDetectionStrategy.Eager, }) class PlainStandaloneDropList { @ViewChild(CdkDropList) dropList!: CdkDropList; @@ -5999,7 +5992,6 @@ class PlainStandaloneDropList { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropZonesWithIntermediateSibling extends ConnectedDropZones {} @@ -6032,7 +6024,6 @@ class ConnectedDropZonesWithIntermediateSibling extends ConnectedDropZones {} `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithInputsInDropZone extends DraggableInDropZone { inputValue = 'hello'; @@ -6055,7 +6046,6 @@ class DraggableWithInputsInDropZone extends DraggableInDropZone { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithRadioInputsInDropZone { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -6107,7 +6097,6 @@ class DraggableWithRadioInputsInDropZone { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedDropZonesWithAlternateContainer extends ConnectedDropZones { override done: string[] = []; @@ -6130,7 +6119,6 @@ class ConnectedDropZonesWithAlternateContainer extends ConnectedDropZones { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithInvalidAlternateContainer { @ViewChildren(CdkDrag) dragItems!: QueryList; @@ -6153,7 +6141,6 @@ class DraggableWithInvalidAlternateContainer { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithMissingAlternateContainer { @ViewChildren(CdkDrag) dragItems!: QueryList; diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index 56b0561dfc57..8ff7eb3654fb 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -194,6 +194,8 @@ export class CdkDropList implements OnDestroy { */ private _unsortedItems = new Set(); + constructor(...args: unknown[]); + constructor() { const config = inject(CDK_DRAG_CONFIG, {optional: true}); const injector = inject(Injector); diff --git a/src/cdk/drag-drop/directives/mixed-drop-list.spec.ts b/src/cdk/drag-drop/directives/mixed-drop-list.spec.ts index 1a4f3f2e549f..6049c69f33f1 100644 --- a/src/cdk/drag-drop/directives/mixed-drop-list.spec.ts +++ b/src/cdk/drag-drop/directives/mixed-drop-list.spec.ts @@ -1,11 +1,5 @@ -import { - Component, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; - +import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {fakeAsync, flush} from '@angular/core/testing'; import {CdkDropList} from './drop-list'; import {CdkDrag} from './drag'; import {moveItemInArray} from '../drag-utils'; @@ -26,7 +20,7 @@ describe('mixed drop list', () => { getSortedSiblings, }); - it('should dispatch the `dropped` event in a wrapping drop zone', async () => { + it('should dispatch the `dropped` event in a wrapping drop zone', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalWrappingDropZone); fixture.detectChanges(); const dragItems = fixture.componentInstance.dragItems; @@ -51,7 +45,7 @@ describe('mixed drop list', () => { seventhItemRect.left + 1, seventhItemRect.top + 1, ); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); @@ -81,29 +75,29 @@ describe('mixed drop list', () => { 'Zero', 'Seven', ]); - }); + })); - it('should move the placeholder as an item is being sorted to the right in a wrapping drop zone', async () => { + it('should move the placeholder as an item is being sorted to the right in a wrapping drop zone', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalWrappingDropZone); fixture.detectChanges(); - await assertStartToEndSorting( + assertStartToEndSorting( 'horizontal', fixture, getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); - it('should move the placeholder as an item is being sorted to the left in a wrapping drop zone', async () => { + it('should move the placeholder as an item is being sorted to the left in a wrapping drop zone', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalWrappingDropZone); fixture.detectChanges(); - await assertEndToStartSorting( + assertEndToStartSorting( 'horizontal', fixture, getSortedSiblings, fixture.componentInstance.dragItems.map(item => item.element.nativeElement), ); - }); + })); }); function getSortedSiblings(item: Element) { @@ -138,7 +132,6 @@ function getSortedSiblings(item: Element) { `, imports: [CdkDropList, CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableInHorizontalWrappingDropZone { @ViewChildren(CdkDrag) dragItems!: QueryList; diff --git a/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts b/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts index 96c9fdd10612..4525a3e193c0 100644 --- a/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts +++ b/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts @@ -1,3 +1,4 @@ +import {fakeAsync, flush} from '@angular/core/testing'; import {dispatchMouseEvent} from '../../testing/private'; import {_supportsShadowDom} from '../../platform'; import {createComponent, startDraggingViaMouse} from './test-utils.spec'; @@ -26,7 +27,7 @@ describe('Single-axis drop list', () => { }, }); - it('should lay out the elements correctly, when swapping down with a taller element', async () => { + it('should lay out the elements correctly, when swapping down with a taller element', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -52,10 +53,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should lay out the elements correctly, when swapping up with a taller element', async () => { + it('should lay out the elements correctly, when swapping up with a taller element', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -81,10 +82,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should lay out elements correctly, when swapping an item with margin', async () => { + it('should lay out elements correctly, when swapping an item with margin', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -110,10 +111,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should lay out the elements correctly, when swapping to the right with a wider element', async () => { + it('should lay out the elements correctly, when swapping to the right with a wider element', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); @@ -138,10 +139,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should lay out the elements correctly, when swapping left with a wider element', async () => { + it('should lay out the elements correctly, when swapping left with a wider element', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); @@ -166,10 +167,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should clear the `transform` value from siblings when item is dropped', async () => { + it('should clear the `transform` value from siblings when item is dropped', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -187,13 +188,13 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(thirdItem.style.transform).toBeFalsy(); - }); + })); - it('should lay out elements correctly, when horizontally swapping an item with margin', async () => { + it('should lay out elements correctly, when horizontally swapping an item with margin', fakeAsync(() => { const fixture = createComponent(DraggableInHorizontalDropZone); fixture.detectChanges(); @@ -218,10 +219,10 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); - it('should preserve the original `transform` of items in the list', async () => { + it('should preserve the original `transform` of items in the list', fakeAsync(() => { const fixture = createComponent(DraggableInScrollableVerticalDropZone); fixture.detectChanges(); const items = fixture.componentInstance.dragItems.map(item => item.element.nativeElement); @@ -259,7 +260,7 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(items.every(hasInitialTransform)) .withContext('Expected items to preserve transform when dragging stops.') @@ -270,9 +271,9 @@ describe('Single-axis drop list', () => { expect(hasInitialTransform(placeholder)) .withContext('Expected placeholder to preserve transform when dragging stops.') .toBe(true); - }); + })); - it('should enter as last child if entering from top in reversed container', async () => { + it('should enter as last child if entering from top in reversed container', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); // Make sure there's only one item in the first list. @@ -309,9 +310,9 @@ describe('Single-axis drop list', () => { .toBe(true); dispatchMouseEvent(document, 'mouseup'); - }); + })); - it('should lay out the elements correctly when scaled', async () => { + it('should lay out the elements correctly when scaled', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.componentInstance.scale = 0.5; fixture.detectChanges(); @@ -333,6 +334,6 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); - }); + flush(); + })); }); diff --git a/src/cdk/drag-drop/directives/standalone-drag.spec.ts b/src/cdk/drag-drop/directives/standalone-drag.spec.ts index e8ecd6b547ed..51dce4d322d4 100644 --- a/src/cdk/drag-drop/directives/standalone-drag.spec.ts +++ b/src/cdk/drag-drop/directives/standalone-drag.spec.ts @@ -10,6 +10,7 @@ import { signal, } from '@angular/core'; import {NgTemplateOutlet} from '@angular/common'; +import {fakeAsync, flush, tick} from '@angular/core/testing'; import { dispatchEvent, createMouseEvent, @@ -21,7 +22,7 @@ import { import {_supportsShadowDom} from '../../platform'; import {CdkDragHandle} from './drag-handle'; import {CdkDrag} from './drag'; -import {CDK_DRAG_CONFIG, DragAxis, DragDropConfig, DragStartDelay} from './config'; +import {CDK_DRAG_CONFIG, DragAxis, DragDropConfig} from './config'; import {DragRef, Point} from '../drag-ref'; import { createComponent, @@ -31,13 +32,12 @@ import { makeScrollable, startDraggingViaMouse, startDraggingViaTouch, - wait, } from './test-utils.spec'; import {isInsideClientRect, isOverflowingParent} from '../dom/dom-rect'; describe('Standalone CdkDrag', () => { describe('mouse dragging', () => { - it('should drag an element freely to a particular position (mouse)', () => { + it('should drag an element freely to a particular position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -45,9 +45,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should reset drag item to boundary', () => { + it('should reset drag item to boundary', fakeAsync(() => { const fixture = createComponent(DragWithResizeableBoundary); fixture.detectChanges(); let boundaryElement = fixture.componentInstance.boundaryElement.nativeElement; @@ -134,9 +134,9 @@ describe('Standalone CdkDrag', () => { dragElement.getBoundingClientRect(), ), ).toBeFalse(); - }); + })); - it('should drag an element freely to a particular position when the page is scrolled (mouse)', () => { + it('should drag an element freely to a particular position when the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -148,9 +148,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); cleanup(); - }); + })); - it('should continue dragging the element from where it was left off (mouse)', () => { + it('should continue dragging the element from where it was left off', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -162,9 +162,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); - }); + })); - it('should continue dragging from where it was left off when the page is scrolled (mouse)', () => { + it('should continue dragging from where it was left off when the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -181,9 +181,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); cleanup(); - }); + })); - it('should not drag an element with the right mouse button', () => { + it('should not drag an element with the right mouse button', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -201,9 +201,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should not drag the element if it was not moved more than the minimum distance', () => { + it('should not drag the element if it was not moved more than the minimum distance', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable, {dragDistance: 5}); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -211,9 +211,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 2, 2); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should be able to stop dragging after a double click', () => { + it('should be able to stop dragging after a double click', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable, {dragDistance: 5}); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -234,9 +234,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should preserve the previous `transform` value', () => { + it('should preserve the previous `transform` value', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -244,9 +244,9 @@ describe('Standalone CdkDrag', () => { dragElement.style.transform = 'translateX(-50%)'; dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px) translateX(-50%)'); - }); + })); - it('should not generate multiple own `translate3d` values', () => { + it('should not generate multiple own `translate3d` values', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -258,9 +258,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px) translateY(-50%)'); - }); + })); - it('should prevent the `mousedown` action for native draggable elements', () => { + it('should prevent the `mousedown` action for native draggable elements', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -277,9 +277,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(mousedownEvent.preventDefault).toHaveBeenCalled(); - }); + })); - it('should not start dragging an element with a fake mousedown event', () => { + it('should not start dragging an element with a fake mousedown event', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -304,9 +304,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should prevent the default dragstart action', () => { + it('should prevent the default dragstart action', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const event = dispatchFakeEvent( @@ -316,9 +316,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not prevent the default dragstart action when dragging is disabled', () => { + it('should not prevent the default dragstart action when dragging is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.dragDisabled.set(true); fixture.detectChanges(); @@ -329,11 +329,11 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(event.defaultPrevented).toBe(false); - }); + })); }); describe('touch dragging', () => { - it('should drag an element freely to a particular position (touch)', () => { + it('should drag an element freely to a particular position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -341,9 +341,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaTouch(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should drag an element freely to a particular position when the page is scrolled (touch)', () => { + it('should drag an element freely to a particular position when the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -355,9 +355,9 @@ describe('Standalone CdkDrag', () => { dragElementViaTouch(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); cleanup(); - }); + })); - it('should continue dragging the element from where it was left off (touch)', () => { + it('should continue dragging the element from where it was left off', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -369,9 +369,9 @@ describe('Standalone CdkDrag', () => { dragElementViaTouch(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); - }); + })); - it('should continue dragging from where it was left off when the page is scrolled (touch)', () => { + it('should continue dragging from where it was left off when the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -388,9 +388,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); cleanup(); - }); + })); - it('should prevent the default `touchmove` action on the page while dragging', () => { + it('should prevent the default `touchmove` action on the page while dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -406,9 +406,9 @@ describe('Standalone CdkDrag', () => { dispatchTouchEvent(document, 'touchend'); fixture.detectChanges(); - }); + })); - it('should not prevent `touchstart` action for native draggable elements', async () => { + it('should not prevent `touchstart` action for native draggable elements', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -425,9 +425,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(touchstartEvent.preventDefault).not.toHaveBeenCalled(); - }); + })); - it('should not start dragging an element with a fake touchstart event', () => { + it('should not start dragging an element with a fake touchstart event', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -453,9 +453,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should stop dragging on touchcancel', () => { + it('should stop dragging on touchcancel', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -469,11 +469,11 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(fixture.componentInstance.endedSpy).toHaveBeenCalled(); - }); + })); }); describe('mouse dragging when initial transform is none', () => { - it('should drag an element freely to a particular position (mouse, initial transform none)', () => { + it('should drag an element freely to a particular position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -481,10 +481,10 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); }); - it('should dispatch an event when the user has started dragging', async () => { + it('should dispatch an event when the user has started dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -500,9 +500,9 @@ describe('Standalone CdkDrag', () => { source: fixture.componentInstance.dragInstance, event: jasmine.anything(), }); - }); + })); - it('should dispatch an event when the user has stopped dragging', () => { + it('should dispatch an event when the user has stopped dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -520,9 +520,9 @@ describe('Standalone CdkDrag', () => { dropPoint: {x: jasmine.any(Number), y: jasmine.any(Number)}, event: jasmine.anything(), }); - }); + })); - it('should include the drag distance in the ended event', () => { + it('should include the drag distance in the ended event', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -545,7 +545,7 @@ describe('Standalone CdkDrag', () => { dropPoint: {x: 40, y: 50}, event: jasmine.anything(), }); - }); + })); it('should emit when the user is moving the drag element', () => { const fixture = createComponent(StandaloneDraggable); @@ -591,7 +591,7 @@ describe('Standalone CdkDrag', () => { subscription.unsubscribe(); }); - it('should be able to lock dragging along the x axis', () => { + it('should be able to lock dragging along the x axis', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.dragLockAxis.set('x'); fixture.detectChanges(); @@ -605,9 +605,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 0px, 0px)'); - }); + })); - it('should be able to lock dragging along the x axis while using constrainPosition', () => { + it('should be able to lock dragging along the x axis while using constrainPosition', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.dragLockAxis.set('x'); fixture.componentInstance.constrainPosition = ( @@ -632,9 +632,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 0px, 0px)'); - }); + })); - it('should be able to lock dragging along the y axis', () => { + it('should be able to lock dragging along the y axis', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.dragLockAxis.set('y'); fixture.detectChanges(); @@ -648,9 +648,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(0px, 300px, 0px)'); - }); + })); - it('should be able to lock dragging along the y axis while using constrainPosition', () => { + it('should be able to lock dragging along the y axis while using constrainPosition', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.dragLockAxis.set('y'); fixture.componentInstance.constrainPosition = ( @@ -675,9 +675,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(0px, 300px, 0px)'); - }); + })); - it('should add a class while an element is being dragged', () => { + it('should add a class while an element is being dragged', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -693,9 +693,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(element.classList).not.toContain('cdk-drag-dragging'); - }); + })); - it('should add a class while an element is being dragged with OnPush change detection', () => { + it('should add a class while an element is being dragged with OnPush change detection', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithOnPush); fixture.detectChanges(); @@ -711,9 +711,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(element.classList).not.toContain('cdk-drag-dragging'); - }); + })); - it('should not add a class if item was not dragged more than the threshold', async () => { + it('should not add a class if item was not dragged more than the threshold', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable, {dragDistance: 5}); fixture.detectChanges(); @@ -724,9 +724,9 @@ describe('Standalone CdkDrag', () => { startDraggingViaMouse(fixture, element); expect(element.classList).not.toContain('cdk-drag-dragging'); - }); + })); - it('should be able to set an alternate drag root element', () => { + it('should be able to set an alternate drag root element', fakeAsync(() => { const fixture = createComponent(DraggableWithAlternateRoot); fixture.componentInstance.rootElementSelector = '.alternate-root'; fixture.changeDetectorRef.markForCheck(); @@ -742,9 +742,9 @@ describe('Standalone CdkDrag', () => { expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should be able to set the cdkDrag element as handle if it has a different root element', () => { + it('should be able to set the cdkDrag element as handle if it has a different root element', fakeAsync(() => { const fixture = createComponent(DraggableWithAlternateRootAndSelfHandle); fixture.detectChanges(); @@ -765,9 +765,9 @@ describe('Standalone CdkDrag', () => { expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should be able to set an alternate drag root element for ng-container', () => { + it('should be able to set an alternate drag root element for ng-container', fakeAsync(() => { const fixture = createComponent(DraggableNgContainerWithAlternateRoot); fixture.detectChanges(); @@ -778,9 +778,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragRoot, 50, 100); expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should preserve the initial transform if the root element changes', () => { + it('should preserve the initial transform if the root element changes', fakeAsync(() => { const fixture = createComponent(DraggableWithAlternateRoot); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -799,12 +799,12 @@ describe('Standalone CdkDrag', () => { expect(alternateRoot.style.transform).not.toContain('translateX(-50%)'); expect(alternateRoot.style.transform).toContain('scale(2)'); - }); + })); - it('should handle the root element selector changing after init', async () => { + it('should handle the root element selector changing after init', fakeAsync(() => { const fixture = createComponent(DraggableWithAlternateRoot); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.componentInstance.rootElementSelector = '.alternate-root'; fixture.changeDetectorRef.markForCheck(); @@ -820,9 +820,9 @@ describe('Standalone CdkDrag', () => { expect(dragRoot.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should not be able to drag the element if dragging is disabled', () => { + it('should not be able to drag the element if dragging is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -836,9 +836,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should enable native drag interactions if dragging is disabled', () => { + it('should enable native drag interactions if dragging is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -850,18 +850,18 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); - }); + })); - it('should enable native drag interactions if not dragging', () => { + it('should enable native drag interactions if not dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; const styles = dragElement.style; expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); - }); + })); - it('should disable native drag interactions if dragging', async () => { + it('should disable native drag interactions if dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -874,9 +874,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); - }); + })); - it('should re-enable drag interactions once dragging is over', () => { + it('should re-enable drag interactions once dragging is over', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -892,9 +892,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); - }); + })); - it('should not stop propagation for the drag sequence start event by default', () => { + it('should not stop propagation for the drag sequence start event by default', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -906,15 +906,15 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(event.stopPropagation).not.toHaveBeenCalled(); - }); + })); - it('should not throw if destroyed before the first change detection run', () => { + it('should not throw if destroyed before the first change detection run', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); expect(() => { fixture.destroy(); }).not.toThrow(); - }); + })); it('should enable native drag interactions on the drag item when there is a handle', () => { const fixture = createComponent(StandaloneDraggableWithHandle); @@ -966,7 +966,7 @@ describe('Standalone CdkDrag', () => { expect(styles.touchAction || (styles as any).webkitUserDrag).toBe('none'); }); - it('should be able to reset a freely-dragged item to its initial position', () => { + it('should be able to reset a freely-dragged item to its initial position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -977,9 +977,9 @@ describe('Standalone CdkDrag', () => { fixture.componentInstance.dragInstance.reset(); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should preserve initial transform after resetting', () => { + it('should preserve initial transform after resetting', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -991,9 +991,9 @@ describe('Standalone CdkDrag', () => { fixture.componentInstance.dragInstance.reset(); expect(dragElement.style.transform).toBe('scale(2)'); - }); + })); - it('should start dragging an item from its initial position after a reset', () => { + it('should start dragging an item from its initial position after a reset', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1005,9 +1005,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 25, 50); expect(dragElement.style.transform).toBe('translate3d(25px, 50px, 0px)'); - }); + })); - it('should not dispatch multiple events for a mouse event right after a touch event', async () => { + it('should not dispatch multiple events for a mouse event right after a touch event', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1018,20 +1018,20 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); dispatchTouchEvent(dragElement, 'touchend'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); // Immediately dispatch a mouse sequence to simulate a fake event. startDraggingViaMouse(fixture, dragElement); fixture.detectChanges(); dispatchMouseEvent(dragElement, 'mouseup'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.startedSpy).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.endedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should round the transform value', () => { + it('should round the transform value', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1039,9 +1039,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 13.37, 37); expect(dragElement.style.transform).toBe('translate3d(13px, 37px, 0px)'); - }); + })); - it('should allow for dragging to be constrained to an element', () => { + it('should allow for dragging to be constrained to an element', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.boundary = '.wrapper'; fixture.detectChanges(); @@ -1050,9 +1050,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 300, 300); expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)'); - }); + })); - it('should allow for dragging to be constrained to an element while using constrainPosition', () => { + it('should allow for dragging to be constrained to an element while using constrainPosition', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.boundary = '.wrapper'; fixture.detectChanges(); @@ -1073,9 +1073,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 300, 300); expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)'); - }); + })); - it('should be able to pass in a DOM node as the boundary', () => { + it('should be able to pass in a DOM node as the boundary', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.boundary = fixture.nativeElement.querySelector('.wrapper'); fixture.detectChanges(); @@ -1084,9 +1084,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 300, 300); expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)'); - }); + })); - it('should adjust the x offset if the boundary becomes narrower after a viewport resize', async () => { + it('should adjust the x offset if the boundary becomes narrower after a viewport resize', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1098,12 +1098,12 @@ describe('Standalone CdkDrag', () => { boundary.style.width = '150px'; dispatchFakeEvent(window, 'resize'); - await wait(20); + tick(20); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should keep the old position if the boundary is invisible after a resize', async () => { + it('should keep the old position if the boundary is invisible after a resize', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1115,12 +1115,12 @@ describe('Standalone CdkDrag', () => { boundary.style.display = 'none'; dispatchFakeEvent(window, 'resize'); - await wait(20); + tick(20); expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)'); - }); + })); - it('should handle the element and boundary dimensions changing between drag sequences', () => { + it('should handle the element and boundary dimensions changing between drag sequences', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1136,9 +1136,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 300, 300); expect(dragElement.style.transform).toBe('translate3d(150px, 150px, 0px)'); - }); + })); - it('should adjust the y offset if the boundary becomes shorter after a viewport resize', async () => { + it('should adjust the y offset if the boundary becomes shorter after a viewport resize', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1150,15 +1150,15 @@ describe('Standalone CdkDrag', () => { boundary.style.height = '150px'; dispatchFakeEvent(window, 'resize'); - await wait(20); + tick(20); expect(dragElement.style.transform).toBe('translate3d(100px, 50px, 0px)'); - }); + })); it( 'should reset the x offset if the boundary becomes narrower than the element ' + 'after a resize', - async () => { + fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1170,13 +1170,13 @@ describe('Standalone CdkDrag', () => { boundary.style.width = '50px'; dispatchFakeEvent(window, 'resize'); - await wait(20); + tick(20); expect(dragElement.style.transform).toBe('translate3d(0px, 100px, 0px)'); - }, + }), ); - it('should reset the y offset if the boundary becomes shorter than the element after a resize', async () => { + it('should reset the y offset if the boundary becomes shorter than the element after a resize', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper'); fixture.componentInstance.boundary = boundary; @@ -1188,12 +1188,12 @@ describe('Standalone CdkDrag', () => { boundary.style.height = '50px'; dispatchFakeEvent(window, 'resize'); - await wait(20); + tick(20); expect(dragElement.style.transform).toBe('translate3d(100px, 0px, 0px)'); - }); + })); - it('should allow for the position constrain logic to be customized', () => { + it('should allow for the position constrain logic to be customized', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); const spy = jasmine.createSpy('constrain position spy').and.returnValue({ x: 50, @@ -1217,7 +1217,7 @@ describe('Standalone CdkDrag', () => { const elementRect = dragElement.getBoundingClientRect(); expect(Math.floor(elementRect.top)).toBe(50); expect(Math.floor(elementRect.left)).toBe(50); - }); + })); it('should throw if drag item is attached to an ng-container', () => { const errorHandler = jasmine.createSpyObj(['handleError']); @@ -1234,7 +1234,7 @@ describe('Standalone CdkDrag', () => { ); }); - it('should cancel drag if the mouse moves before the delay is elapsed', async () => { + it('should cancel drag if the mouse moves before the delay is elapsed', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -1257,9 +1257,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected element not to be moved if the mouse moved before the delay.') .toBeFalsy(); - }); + })); - it('should enable native drag interactions if mouse moves before the delay', async () => { + it('should enable native drag interactions if mouse moves before the delay', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -1281,9 +1281,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy(); - }); + })); - it('should allow dragging after the drag start delay is elapsed', () => { + it('should allow dragging after the drag start delay is elapsed', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -1309,9 +1309,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected element to be dragged after all the time has passed.') .toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should not prevent the default touch action before the delay has elapsed', async () => { + it('should not prevent the default touch action before the delay has elapsed', fakeAsync(() => { spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -1329,15 +1329,15 @@ describe('Standalone CdkDrag', () => { currentTime += 250; expect(dispatchTouchEvent(document, 'touchmove', 50, 100).defaultPrevented).toBe(false); - }); + })); - it('should handle the drag delay as a string', () => { + it('should handle the drag delay as a string', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; const fixture = createComponent(StandaloneDraggable); - fixture.componentInstance.dragStartDelay = '500' as any; + fixture.componentInstance.dragStartDelay = '500'; fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1357,9 +1357,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected element to be dragged after all the time has passed.') .toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should be able to configure the drag start delay based on the event type', () => { + it('should be able to configure the drag start delay based on the event type', fakeAsync(() => { // We can't use Jasmine's `clock` because Zone.js interferes with it. spyOn(Date, 'now').and.callFake(() => currentTime); let currentTime = 0; @@ -1382,9 +1382,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected element to be moved via mouse because it has no delay.') .toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should be able to get the current position', () => { + it('should be able to get the current position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1398,9 +1398,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragInstance.getFreeDragPosition()).toEqual({x: 150, y: 300}); - }); + })); - it('should be able to set the current position programmatically', () => { + it('should be able to set the current position programmatically', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1411,9 +1411,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(dragInstance.getFreeDragPosition()).toEqual({x: 50, y: 100}); - }); + })); - it('should be able to set the current position', () => { + it('should be able to set the current position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.freeDragPosition = {x: 50, y: 100}; fixture.changeDetectorRef.markForCheck(); @@ -1424,9 +1424,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); expect(dragInstance.getFreeDragPosition()).toEqual({x: 50, y: 100}); - }); + })); - it('should be able to get the up-to-date position as the user is dragging', async () => { + it('should be able to get the up-to-date position as the user is dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1445,9 +1445,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragInstance.getFreeDragPosition()).toEqual({x: 100, y: 200}); - }); + })); - it('should react to changes in the free drag position', () => { + it('should react to changes in the free drag position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.freeDragPosition = {x: 50, y: 100}; fixture.changeDetectorRef.markForCheck(); @@ -1462,9 +1462,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect(dragElement.style.transform).toBe('translate3d(100px, 200px, 0px)'); - }); + })); - it('should be able to continue dragging after the current position was set', () => { + it('should be able to continue dragging after the current position was set', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.freeDragPosition = {x: 50, y: 100}; fixture.changeDetectorRef.markForCheck(); @@ -1476,9 +1476,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, dragElement, 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); - }); + })); - it('should account for the scale when setting the free drag position', () => { + it('should account for the scale when setting the free drag position', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.scale = 0.5; fixture.componentInstance.freeDragPosition = {x: 50, y: 100}; @@ -1490,9 +1490,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(100px, 200px, 0px)'); expect(dragInstance.getFreeDragPosition()).toEqual({x: 50, y: 100}); - }); + })); - it('should include the dragged distance as the user is dragging', async () => { + it('should include the dragged distance as the user is dragging', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1514,9 +1514,9 @@ describe('Standalone CdkDrag', () => { expect(event.distance).toEqual({x: 75, y: 50}); subscription.unsubscribe(); - }); + })); - it('should be able to configure the drag input defaults through a provider', () => { + it('should be able to configure the drag input defaults through a provider', fakeAsync(() => { const config: DragDropConfig = { draggingDisabled: true, dragStartDelay: 1337, @@ -1546,9 +1546,9 @@ describe('Standalone CdkDrag', () => { expect(drag.boundaryElement).toBe('.boundary'); expect(drag.rootElementSelector).toBe('.root'); expect(drag.previewContainer).toBe('parent'); - }); + })); - it('should not throw if touches and changedTouches are empty', () => { + it('should not throw if touches and changedTouches are empty', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1556,19 +1556,20 @@ describe('Standalone CdkDrag', () => { startDraggingViaTouch(fixture, dragElement); continueDraggingViaTouch(fixture, 50, 100); - expect(() => { - const event = createTouchEvent('touchend', 50, 100); - Object.defineProperties(event, { - touches: {get: () => []}, - changedTouches: {get: () => []}, - }); + const event = createTouchEvent('touchend', 50, 100); + Object.defineProperties(event, { + touches: {get: () => []}, + changedTouches: {get: () => []}, + }); + expect(() => { dispatchEvent(document, event); fixture.detectChanges(); + tick(); }).not.toThrow(); - }); + })); - it('should update the free drag position if the page is scrolled', async () => { + it('should update the free drag position if the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1588,9 +1589,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(50px, 600px, 0px)'); cleanup(); - }); + })); - it('should update the free drag position if the user moves their pointer after the page is scrolled', async () => { + it('should update the free drag position if the user moves their pointer after the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -1613,9 +1614,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(50px, 700px, 0px)'); cleanup(); - }); + })); - it('should account for scale when moving the element', () => { + it('should account for scale when moving the element', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.componentInstance.scale = 0.5; fixture.detectChanges(); @@ -1624,10 +1625,10 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBe('translate3d(100px, 200px, 0px)'); - }); + })); describe('with a handle', () => { - it('should not be able to drag the entire element if it has a handle', () => { + it('should not be able to drag the entire element if it has a handle', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1635,9 +1636,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, dragElement, 50, 100); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should be able to drag an element using its handle', () => { + it('should be able to drag an element using its handle', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1646,9 +1647,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handle, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should not be able to drag the element if the handle is disabled', () => { + it('should not be able to drag the element if the handle is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1659,9 +1660,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handle, 50, 100); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should not be able to drag the element if the handle is disabled before init', () => { + it('should not be able to drag the element if the handle is disabled before init', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithPreDisabledHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1670,9 +1671,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handle, 50, 100); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should not be able to drag using the handle if the element is disabled', () => { + it('should not be able to drag using the handle if the element is disabled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1685,9 +1686,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handle, 50, 100); expect(dragElement.style.transform).toBeFalsy(); - }); + })); - it('should be able to use a handle that was added after init', () => { + it('should be able to use a handle that was added after init', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithDelayedHandle); fixture.detectChanges(); @@ -1701,9 +1702,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handle, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should be able to use more than one handle to drag the element', () => { + it('should be able to use more than one handle to drag the element', fakeAsync(async () => { const fixture = createComponent(StandaloneDraggableWithMultipleHandles); fixture.detectChanges(); @@ -1716,9 +1717,9 @@ describe('Standalone CdkDrag', () => { dragElementViaMouse(fixture, handles[0], 100, 200); expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)'); - }); + })); - it('should be able to drag with a handle that is not a direct descendant', () => { + it('should be able to drag with a handle that is not a direct descendant', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithIndirectHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1735,9 +1736,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected to drag the element by its handle.') .toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should be able to drag with a handle that is defined in a separate embedded view', () => { + it('should be able to drag with a handle that is defined in a separate embedded view', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithExternalTemplateHandle); fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; @@ -1754,9 +1755,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform) .withContext('Expected to drag the element by its handle.') .toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should disable the tap highlight while dragging via the handle', () => { + it('should disable the tap highlight while dragging via the handle', fakeAsync(() => { // This test is irrelevant if the browser doesn't support styling the tap highlight color. if (!('webkitTapHighlightColor' in document.body.style)) { return; @@ -1780,9 +1781,9 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect((dragElement.style as any).webkitTapHighlightColor).toBeFalsy(); - }); + })); - it('should preserve any existing `webkitTapHighlightColor`', () => { + it('should preserve any existing `webkitTapHighlightColor`', fakeAsync(() => { // This test is irrelevant if the browser doesn't support styling the tap highlight color. if (!('webkitTapHighlightColor' in document.body.style)) { return; @@ -1806,15 +1807,16 @@ describe('Standalone CdkDrag', () => { fixture.detectChanges(); expect((dragElement.style as any).webkitTapHighlightColor).toBe('purple'); - }); + })); - it('should throw if drag handle is attached to an ng-container', () => { + it('should throw if drag handle is attached to an ng-container', fakeAsync(() => { expect(() => { createComponent(DragHandleOnNgContainer).detectChanges(); + flush(); }).toThrowError(/^cdkDragHandle must be attached to an element node/); - }); + })); - it('should be able to drag an element using a handle with a shadow DOM child', () => { + it('should be able to drag an element using a handle with a shadow DOM child', fakeAsync(() => { if (!_supportsShadowDom()) { return; } @@ -1827,9 +1829,9 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBeFalsy(); dragElementViaMouse(fixture, handleChild, 50, 100); expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - }); + })); - it('should prevent default dragStart on handle, not on entire draggable', () => { + it('should prevent default dragStart on handle, not on entire draggable', fakeAsync(() => { const fixture = createComponent(StandaloneDraggableWithHandle); fixture.detectChanges(); @@ -1848,7 +1850,7 @@ describe('Standalone CdkDrag', () => { expect(draggableEvent.defaultPrevented).toBe(false); expect(handleEvent.defaultPrevented).toBe(true); - }); + })); }); }); @@ -1872,7 +1874,6 @@ describe('Standalone CdkDrag', () => { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggable { @ViewChild('dragElement') dragElement!: ElementRef; @@ -1880,8 +1881,8 @@ class StandaloneDraggable { startedSpy = jasmine.createSpy('started spy'); endedSpy = jasmine.createSpy('ended spy'); releasedSpy = jasmine.createSpy('released spy'); - boundary!: string | HTMLElement; - dragStartDelay!: DragStartDelay; + boundary: string | HTMLElement | undefined; + dragStartDelay: number | string | {touch: number; mouse: number} | undefined; constrainPosition: | (( userPointerPosition: Point, @@ -1890,13 +1891,14 @@ class StandaloneDraggable { pickupPositionInElement: Point, ) => Point) | undefined; - freeDragPosition!: {x: number; y: number}; + freeDragPosition?: {x: number; y: number}; dragDisabled = signal(false); - dragLockAxis = signal(null); + dragLockAxis = signal(undefined); scale = 1; } @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, template: `
    `, @@ -1915,7 +1917,6 @@ class StandaloneDraggableWithOnPush { `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -1937,7 +1938,6 @@ class StandaloneDraggableWithHandle { `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithPreDisabledHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -1958,7 +1958,6 @@ class StandaloneDraggableWithPreDisabledHandle { `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithDelayedHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -1973,7 +1972,6 @@ class StandaloneDraggableWithDelayedHandle { @Component({ selector: 'passthrough-component', template: '', - changeDetection: ChangeDetectionStrategy.Eager, }) class PassthroughComponent {} @@ -1991,7 +1989,6 @@ class PassthroughComponent {} `, imports: [CdkDrag, CdkDragHandle, PassthroughComponent], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithIndirectHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -2002,7 +1999,6 @@ class StandaloneDraggableWithIndirectHandle { selector: 'shadow-wrapper', template: '', encapsulation: ViewEncapsulation.ShadowDom, - changeDetection: ChangeDetectionStrategy.Eager, }) class ShadowWrapper {} @@ -2017,7 +2013,6 @@ class ShadowWrapper {} `, imports: [CdkDrag, CdkDragHandle, ShadowWrapper], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithShadowInsideHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -2043,7 +2038,6 @@ class StandaloneDraggableWithShadowInsideHandle { `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithMultipleHandles { @ViewChild('dragElement') dragElement!: ElementRef; @@ -2061,13 +2055,12 @@ class StandaloneDraggableWithMultipleHandles { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithAlternateRoot { @ViewChild('dragElement') dragElement!: ElementRef; @ViewChild('dragRoot') dragRoot!: ElementRef; @ViewChild(CdkDrag) dragInstance!: CdkDrag; - rootElementSelector!: string; + rootElementSelector: string | undefined; } @Component({ @@ -2075,7 +2068,6 @@ class DraggableWithAlternateRoot { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableOnNgContainer {} @@ -2086,7 +2078,6 @@ class DraggableOnNgContainer {} `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DragHandleOnNgContainer {} @@ -2102,7 +2093,6 @@ class DragHandleOnNgContainer {} `, imports: [CdkDrag, CdkDragHandle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableWithAlternateRootAndSelfHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -2119,7 +2109,6 @@ class DraggableWithAlternateRootAndSelfHandle { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DraggableNgContainerWithAlternateRoot { @ViewChild('dragRoot') dragRoot!: ElementRef; @@ -2129,7 +2118,6 @@ class DraggableNgContainerWithAlternateRoot { @Component({ template: `
    `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class PlainStandaloneDraggable { @ViewChild(CdkDrag) dragInstance!: CdkDrag; @@ -2147,7 +2135,6 @@ class PlainStandaloneDraggable { `, imports: [CdkDrag, CdkDragHandle, NgTemplateOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggableWithExternalTemplateHandle { @ViewChild('dragElement') dragElement!: ElementRef; @@ -2162,7 +2149,6 @@ class StandaloneDraggableWithExternalTemplateHandle { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class DragWithResizeableBoundary { @ViewChild('boundaryElement') boundaryElement!: ElementRef; diff --git a/src/cdk/drag-drop/directives/standalone-drag.zone.spec.ts b/src/cdk/drag-drop/directives/standalone-drag.zone.spec.ts index 114584b00814..8e8512c8d29a 100644 --- a/src/cdk/drag-drop/directives/standalone-drag.zone.spec.ts +++ b/src/cdk/drag-drop/directives/standalone-drag.zone.spec.ts @@ -5,13 +5,11 @@ import { Type, ViewChild, provideZoneChangeDetection, - ChangeDetectionStrategy, } from '@angular/core'; import {Point} from '../drag-ref'; import {CdkDrag} from './drag'; import {createComponent as _createComponent, dragElementViaMouse} from './test-utils.spec'; import {ComponentFixture} from '@angular/core/testing'; -import {DragStartDelay} from './config'; describe('Standalone CdkDrag Zone.js integration', () => { function createComponent(type: Type): ComponentFixture { @@ -54,7 +52,6 @@ describe('Standalone CdkDrag Zone.js integration', () => { `, imports: [CdkDrag], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneDraggable { @ViewChild('dragElement') dragElement!: ElementRef; @@ -62,8 +59,8 @@ class StandaloneDraggable { startedSpy = jasmine.createSpy('started spy'); endedSpy = jasmine.createSpy('ended spy'); releasedSpy = jasmine.createSpy('released spy'); - boundary!: string | HTMLElement; - dragStartDelay!: DragStartDelay; - constrainPosition!: (point: Point) => Point; - freeDragPosition!: {x: number; y: number}; + boundary: string | HTMLElement | undefined; + dragStartDelay: number | string | {touch: number; mouse: number} | undefined; + constrainPosition: ((point: Point) => Point) | undefined; + freeDragPosition: {x: number; y: number} | undefined; } diff --git a/src/cdk/drag-drop/directives/test-utils.spec.ts b/src/cdk/drag-drop/directives/test-utils.spec.ts index 21ef8cb7afdd..9a8874e95d3c 100644 --- a/src/cdk/drag-drop/directives/test-utils.spec.ts +++ b/src/cdk/drag-drop/directives/test-utils.spec.ts @@ -1,5 +1,5 @@ import {EnvironmentProviders, Provider, Type, ViewEncapsulation} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, tick} from '@angular/core/testing'; import {dispatchMouseEvent, dispatchTouchEvent} from '../../testing/private'; import {CDK_DRAG_CONFIG, DragDropConfig, DropListOrientation} from './config'; @@ -152,7 +152,6 @@ export function makeScrollable( veryTallElement.style.width = direction === 'vertical' ? '100%' : '4000px'; veryTallElement.style.height = direction === 'vertical' ? '2000px' : '5px'; element.prepend(veryTallElement); - scrollTo(0, 0); return () => { scrollTo(0, 0); @@ -160,6 +159,7 @@ export function makeScrollable( }; } -export function wait(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); +/** Ticks the specified amount of `requestAnimationFrame`-s. */ +export function tickAnimationFrames(amount: number) { + tick(16.6 * amount); // Angular turns rAF calls into 16.6ms timeouts in tests. } diff --git a/src/cdk/drag-drop/drag-drop-registry.spec.ts b/src/cdk/drag-drop/drag-drop-registry.spec.ts index a91f8e61a14c..f32e9d295153 100644 --- a/src/cdk/drag-drop/drag-drop-registry.spec.ts +++ b/src/cdk/drag-drop/drag-drop-registry.spec.ts @@ -1,5 +1,5 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed, ComponentFixture} from '@angular/core/testing'; +import {Component} from '@angular/core'; +import {fakeAsync, TestBed, ComponentFixture} from '@angular/core/testing'; import { createMouseEvent, dispatchMouseEvent, @@ -15,11 +15,11 @@ describe('DragDropRegistry', () => { let fixture: ComponentFixture; let registry: DragDropRegistry; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(BlankComponent); fixture.detectChanges(); registry = TestBed.inject(DragDropRegistry); - }); + })); it('should be able to start dragging an item', () => { const item = new DragItem() as unknown as DragRef; @@ -261,7 +261,6 @@ describe('DragDropRegistry', () => { @Component({ template: ``, imports: [DragDropModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BlankComponent {} }); diff --git a/src/cdk/drag-drop/drag-drop-registry.ts b/src/cdk/drag-drop/drag-drop-registry.ts index 71e93cd9f415..358b4a23daaf 100644 --- a/src/cdk/drag-drop/drag-drop-registry.ts +++ b/src/cdk/drag-drop/drag-drop-registry.ts @@ -7,8 +7,9 @@ */ import { + ChangeDetectionStrategy, Component, - Service, + Injectable, ListenerOptions, NgZone, OnDestroy, @@ -45,6 +46,7 @@ const activeCapturingEventOptions = { styleUrl: 'resets.css', encapsulation: ViewEncapsulation.None, template: '', + changeDetection: ChangeDetectionStrategy.OnPush, host: {'cdk-drag-resets-container': ''}, }) export class _ResetsLoader {} @@ -54,7 +56,7 @@ export class _ResetsLoader {} * instances, and manages global event listeners on the `document`. * @docs-private */ -@Service() +@Injectable({providedIn: 'root'}) export class DragDropRegistry implements OnDestroy { private _ngZone = inject(NgZone); private _document = inject(DOCUMENT); @@ -100,6 +102,9 @@ export class DragDropRegistry implements OnDestroy { */ readonly pointerUp: Subject = new Subject(); + constructor(...args: unknown[]); + constructor() {} + /** Adds a drop container to the registry. */ registerDropContainer(drop: DropListRef) { if (!this._dropInstances.has(drop)) { diff --git a/src/cdk/drag-drop/drag-drop.spec.ts b/src/cdk/drag-drop/drag-drop.spec.ts index 3f7203f350c5..12c797f49345 100644 --- a/src/cdk/drag-drop/drag-drop.spec.ts +++ b/src/cdk/drag-drop/drag-drop.spec.ts @@ -1,5 +1,5 @@ -import {Component, ElementRef, inject, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ElementRef, inject} from '@angular/core'; +import {TestBed, fakeAsync} from '@angular/core/testing'; import {DragDrop} from './drag-drop'; import {DragRef} from './drag-ref'; import {DropListRef} from './drop-list-ref'; @@ -7,9 +7,9 @@ import {DropListRef} from './drop-list-ref'; describe('DragDrop', () => { let service: DragDrop; - beforeEach(() => { + beforeEach(fakeAsync(() => { service = TestBed.inject(DragDrop); - }); + })); it('should be able to attach a DragRef to a DOM node', () => { const fixture = TestBed.createComponent(TestComponent); @@ -30,7 +30,6 @@ describe('DragDrop', () => { @Component({ template: '
    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class TestComponent { elementRef = inject>(ElementRef); diff --git a/src/cdk/drag-drop/drag-drop.ts b/src/cdk/drag-drop/drag-drop.ts index 4c39fba1ae5d..5b253e26069f 100644 --- a/src/cdk/drag-drop/drag-drop.ts +++ b/src/cdk/drag-drop/drag-drop.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, ElementRef, inject, Injector} from '@angular/core'; +import {Injectable, ElementRef, inject, Injector} from '@angular/core'; import {createDragRef, DragRef, DragRefConfig} from './drag-ref'; import {createDropListRef, DropListRef} from './drop-list-ref'; @@ -16,10 +16,13 @@ import {createDropListRef, DropListRef} from './drop-list-ref'; * Will be removed in v23. * @breaking-change 23.0.0 */ -@Service() +@Injectable({providedIn: 'root'}) export class DragDrop { private _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() {} + /** * Turns an element into a draggable item. * @param element Element to which to attach the dragging functionality. diff --git a/src/cdk/drag-drop/drag-utils.spec.ts b/src/cdk/drag-drop/drag-utils.spec.ts index 13aee75ee87c..e92e784d5f2f 100644 --- a/src/cdk/drag-drop/drag-utils.spec.ts +++ b/src/cdk/drag-drop/drag-utils.spec.ts @@ -21,7 +21,7 @@ describe('dragging utilities', () => { expect(array).toEqual([1, 2, 3, 0]); }); - it('should handle an index less than zero when moving', () => { + it('should handle an index less than zero', () => { moveItemInArray(array, 3, -1); expect(array).toEqual([3, 0, 1, 2]); }); @@ -37,7 +37,7 @@ describe('dragging utilities', () => { expect(b).toEqual([3, 4, 1, 5]); }); - it('should handle an index greater than the target array length when transferring', () => { + it('should handle an index greater than the target array length', () => { const a = [0, 1, 2]; const b = [3, 4, 5]; @@ -47,7 +47,7 @@ describe('dragging utilities', () => { expect(b).toEqual([3, 4, 5, 0]); }); - it('should handle an index less than zero when transferring', () => { + it('should handle an index less than zero', () => { const a = [0, 1, 2]; const b = [3, 4, 5]; @@ -56,7 +56,7 @@ describe('dragging utilities', () => { expect(b).toEqual([2, 3, 4, 5]); }); - it('should not do anything if the source array is empty when transferring', () => { + it('should not do anything if the source array is empty', () => { const a: number[] = []; const b = [3, 4, 5]; @@ -76,7 +76,7 @@ describe('dragging utilities', () => { expect(b).toEqual([3, 4, 1, 5]); }); - it('should handle an index greater than the target array length when copying', () => { + it('should handle an index greater than the target array length', () => { const a = [0, 1, 2]; const b = [3, 4, 5]; @@ -86,7 +86,7 @@ describe('dragging utilities', () => { expect(b).toEqual([3, 4, 5, 0]); }); - it('should handle an index less than zero when copying', () => { + it('should handle an index less than zero', () => { const a = [0, 1, 2]; const b = [3, 4, 5]; @@ -95,7 +95,7 @@ describe('dragging utilities', () => { expect(b).toEqual([2, 3, 4, 5]); }); - it('should not do anything if the source array is empty when copying', () => { + it('should not do anything if the source array is empty', () => { const a: number[] = []; const b = [3, 4, 5]; diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts index e37665a6442c..000b3c188691 100644 --- a/src/cdk/drag-drop/drop-list-ref.ts +++ b/src/cdk/drag-drop/drop-list-ref.ts @@ -301,6 +301,8 @@ export class DropListRef { * container when the item was dropped. * @param distance Distance the user has dragged since the start of the dragging sequence. * @param event Event that triggered the dropping sequence. + * + * @breaking-change 15.0.0 `previousIndex` and `event` parameters to become required. */ drop( item: DragRef, @@ -310,7 +312,7 @@ export class DropListRef { isPointerOverContainer: boolean, distance: Point, dropPoint: Point, - event: MouseEvent | TouchEvent, + event: MouseEvent | TouchEvent = {} as any, ): void { this._reset(); this.dropped.next({ diff --git a/src/cdk/drag-drop/resets.scss b/src/cdk/drag-drop/resets.scss index 23a7a7d1b247..123953f8de6a 100644 --- a/src/cdk/drag-drop/resets.scss +++ b/src/cdk/drag-drop/resets.scss @@ -4,7 +4,6 @@ border: none; padding: 0; color: inherit; - overflow: visible; // Chrome sets a user agent style of `inset: 0` which combined // with `align-self` can break the positioning (see #29809). diff --git a/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts b/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts index 3f707ee0b1bd..648f52cf6b5f 100644 --- a/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts +++ b/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts @@ -168,17 +168,6 @@ export class SingleAxisSortStrategy implements DropListSortStrategy { * out automatically. */ enter(item: DragRef, pointerX: number, pointerY: number, index?: number): void { - const activeDraggables = this._activeDraggables; - const currentIndex = activeDraggables.indexOf(item); - const placeholder = item.getPlaceholderElement(); - - // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it - // into another container and back again), we have to ensure that it isn't duplicated. - // Note that we need to run this early so the code further below isn't thrown off. - if (currentIndex > -1) { - activeDraggables.splice(currentIndex, 1); - } - const newIndex = index == null || index < 0 ? // We use the coordinates of where the item entered the drop @@ -186,6 +175,9 @@ export class SingleAxisSortStrategy implements DropListSortStrategy { this._getItemIndexFromPointerPosition(item, pointerX, pointerY) : index; + const activeDraggables = this._activeDraggables; + const currentIndex = activeDraggables.indexOf(item); + const placeholder = item.getPlaceholderElement(); let newPositionReference: DragRef | undefined = activeDraggables[newIndex]; // If the item at the new position is the same as the item that is being dragged, @@ -205,6 +197,12 @@ export class SingleAxisSortStrategy implements DropListSortStrategy { newPositionReference = activeDraggables[0]; } + // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it + // into another container and back again), we have to ensure that it isn't duplicated. + if (currentIndex > -1) { + activeDraggables.splice(currentIndex, 1); + } + // Don't use items that are being dragged as a reference, because // their element has been moved down to the bottom of the body. if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) { diff --git a/src/cdk/layout/breakpoints-observer.md b/src/cdk/layout/breakpoints-observer.md index d8156e437a4c..c5dfc710b58b 100644 --- a/src/cdk/layout/breakpoints-observer.md +++ b/src/cdk/layout/breakpoints-observer.md @@ -1,7 +1,7 @@ -### BreakpointObserver +### BreakpointsModule -`BreakpointObserver` is an injectable service that lets you evaluate media queries to determine -the current screen size and react to changes when the viewport size crosses a breakpoint. +When including the CDK's `LayoutModule`, components can inject `BreakpointsObserver` to request +the matching state of a CSS Media Query. A set of breakpoints is provided based on the Material Design [breakpoint system](https://material.io/guidelines/layout/responsive-ui.html#responsive-ui-breakpoints). @@ -10,10 +10,10 @@ A set of breakpoints is provided based on the Material Design ```ts @Component({ ... }) export class MyWidget { - private breakpointObserver = inject(BreakpointObserver); + isHandset: Observable; - constructor() { - this.breakpointObserver.observe(Handset).subscribe((state: BreakpointState) => { + constructor(bm: BreakpointObserver) { + bm.observe(Handset).subscribe((state: BreakpointState) => { if (state.matches) { this.makeEverythingFitOnSmallScreen(); } else { diff --git a/src/cdk/layout/breakpoints-observer.spec.ts b/src/cdk/layout/breakpoints-observer.spec.ts index 152dcbf04312..89374f8bff19 100644 --- a/src/cdk/layout/breakpoints-observer.spec.ts +++ b/src/cdk/layout/breakpoints-observer.spec.ts @@ -8,38 +8,33 @@ import {BreakpointObserver, BreakpointState} from './breakpoints-observer'; import {MediaMatcher} from './media-matcher'; -import {TestBed} from '@angular/core/testing'; -import {Service} from '@angular/core'; +import {fakeAsync, TestBed, flush, tick} from '@angular/core/testing'; +import {Injectable} from '@angular/core'; import {Subscription} from 'rxjs'; import {skip, take} from 'rxjs/operators'; describe('BreakpointObserver', () => { - const debounceTime = 20; let breakpointObserver: BreakpointObserver; let mediaMatcher: FakeMediaMatcher; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [{provide: MediaMatcher, useClass: FakeMediaMatcher}], }); breakpointObserver = TestBed.inject(BreakpointObserver); mediaMatcher = TestBed.inject(MediaMatcher) as unknown as FakeMediaMatcher; - }); + })); afterEach(() => { mediaMatcher.clear(); }); - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - it('retrieves the whether a query is currently matched', () => { + it('retrieves the whether a query is currently matched', fakeAsync(() => { const query = 'everything starts as true in the FakeMediaMatcher'; expect(breakpointObserver.isMatched(query)).toBeTruthy(); - }); + })); - it('reuses the same MediaQueryList for matching queries', () => { + it('reuses the same MediaQueryList for matching queries', fakeAsync(() => { expect(mediaMatcher.queryCount).toBe(0); breakpointObserver.observe('query1'); expect(mediaMatcher.queryCount).toBe(1); @@ -49,9 +44,9 @@ describe('BreakpointObserver', () => { expect(mediaMatcher.queryCount).toBe(2); breakpointObserver.observe('query1'); expect(mediaMatcher.queryCount).toBe(2); - }); + })); - it('splits combined query strings into individual matchMedia listeners', () => { + it('splits combined query strings into individual matchMedia listeners', fakeAsync(() => { expect(mediaMatcher.queryCount).toBe(0); breakpointObserver.observe('query1, query2'); expect(mediaMatcher.queryCount).toBe(2); @@ -59,44 +54,46 @@ describe('BreakpointObserver', () => { expect(mediaMatcher.queryCount).toBe(2); breakpointObserver.observe('query2, query3'); expect(mediaMatcher.queryCount).toBe(3); - }); + })); - it('accepts an array of queries', () => { + it('accepts an array of queries', fakeAsync(() => { const queries = ['1 query', '2 query', 'red query', 'blue query']; breakpointObserver.observe(queries); expect(mediaMatcher.queryCount).toBe(queries.length); - }); + })); - it('completes all events when the breakpoint manager is destroyed', () => { + it('completes all events when the breakpoint manager is destroyed', fakeAsync(() => { const firstTest = jasmine.createSpy('test1'); breakpointObserver.observe('test1').subscribe({complete: firstTest}); const secondTest = jasmine.createSpy('test2'); breakpointObserver.observe('test2').subscribe({complete: secondTest}); + flush(); expect(firstTest).not.toHaveBeenCalled(); expect(secondTest).not.toHaveBeenCalled(); breakpointObserver.ngOnDestroy(); + flush(); expect(firstTest).toHaveBeenCalled(); expect(secondTest).toHaveBeenCalled(); - }); + })); - it('emits an event on the observable when values change', async () => { + it('emits an event on the observable when values change', fakeAsync(() => { const query = '(width: 999px)'; let queryMatchState = false; breakpointObserver.observe(query).subscribe((state: BreakpointState) => { queryMatchState = state.matches; }); - await wait(debounceTime); + tick(); expect(queryMatchState).toBeTruthy(); mediaMatcher.setMatchesQuery(query, false); - await wait(debounceTime); + tick(); expect(queryMatchState).toBeFalsy(); - }); + })); - it('emits an event on the observable with the matching state of all queries provided', async () => { + it('emits an event on the observable with the matching state of all queries provided', fakeAsync(() => { const queryOne = '(width: 999px)'; const queryTwo = '(width: 700px)'; let state: BreakpointState = {matches: false, breakpoints: {}}; @@ -107,31 +104,32 @@ describe('BreakpointObserver', () => { mediaMatcher.setMatchesQuery(queryOne, false); mediaMatcher.setMatchesQuery(queryTwo, false); - await wait(debounceTime); + tick(); expect(state.breakpoints).toEqual({[queryOne]: false, [queryTwo]: false}); mediaMatcher.setMatchesQuery(queryOne, true); mediaMatcher.setMatchesQuery(queryTwo, false); - await wait(debounceTime); + tick(); expect(state.breakpoints).toEqual({[queryOne]: true, [queryTwo]: false}); - }); + })); - it('emits a true matches state when the query is matched', () => { + it('emits a true matches state when the query is matched', fakeAsync(() => { const query = '(width: 999px)'; breakpointObserver.observe(query).subscribe(); mediaMatcher.setMatchesQuery(query, true); + tick(); expect(breakpointObserver.isMatched(query)).toBeTruthy(); - }); + })); - it('emits a false matches state when the query is not matched', async () => { + it('emits a false matches state when the query is not matched', fakeAsync(() => { const query = '(width: 999px)'; breakpointObserver.observe(query).subscribe(); mediaMatcher.setMatchesQuery(query, false); - await wait(debounceTime); + tick(); expect(breakpointObserver.isMatched(query)).toBeFalsy(); - }); + })); - it('emits one event when multiple queries change', async () => { + it('emits one event when multiple queries change', fakeAsync(() => { const observer = jasmine.createSpy('observer'); const queryOne = '(width: 700px)'; const queryTwo = '(width: 999px)'; @@ -140,11 +138,11 @@ describe('BreakpointObserver', () => { mediaMatcher.setMatchesQuery(queryOne, false); mediaMatcher.setMatchesQuery(queryTwo, false); - await wait(debounceTime); + tick(); expect(observer).toHaveBeenCalledTimes(1); - }); + })); - it('should not complete other subscribers when preceding subscriber completes', async () => { + it('should not complete other subscribers when preceding subscriber completes', fakeAsync(() => { const queryOne = '(width: 700px)'; const queryTwo = '(width: 999px)'; const breakpoint = breakpointObserver.observe([queryOne, queryTwo]).pipe(skip(1)); @@ -158,19 +156,19 @@ describe('BreakpointObserver', () => { mediaMatcher.setMatchesQuery(queryOne, true); mediaMatcher.setMatchesQuery(queryTwo, false); - await wait(debounceTime); + tick(); expect(emittedValues).toEqual([1, 2, 3, 4]); emittedValues = []; mediaMatcher.setMatchesQuery(queryOne, false); mediaMatcher.setMatchesQuery(queryTwo, true); - await wait(debounceTime); + tick(); expect(emittedValues).toEqual([1, 3, 4]); subscriptions.forEach(subscription => subscription.unsubscribe()); - }); + })); }); export class FakeMediaQueryList { @@ -207,7 +205,7 @@ export class FakeMediaQueryList { } } -@Service({autoProvided: false}) +@Injectable() export class FakeMediaMatcher { /** A map of match media queries. */ private _queries = new Map(); diff --git a/src/cdk/layout/breakpoints-observer.ts b/src/cdk/layout/breakpoints-observer.ts index b18abfbd026b..5373c85ecd63 100644 --- a/src/cdk/layout/breakpoints-observer.ts +++ b/src/cdk/layout/breakpoints-observer.ts @@ -7,7 +7,7 @@ */ import {coerceArray} from '../coercion'; -import {Service, NgZone, OnDestroy, inject} from '@angular/core'; +import {Injectable, NgZone, OnDestroy, inject} from '@angular/core'; import {combineLatest, concat, Observable, Observer, Subject} from 'rxjs'; import {debounceTime, map, skip, startWith, take, takeUntil} from 'rxjs/operators'; import {MediaMatcher} from './media-matcher'; @@ -39,7 +39,7 @@ interface Query { } /** Utility for checking the matching state of `@media` queries. */ -@Service() +@Injectable({providedIn: 'root'}) export class BreakpointObserver implements OnDestroy { private _mediaMatcher = inject(MediaMatcher); private _zone = inject(NgZone); @@ -49,6 +49,9 @@ export class BreakpointObserver implements OnDestroy { /** A subject for all other observables to takeUntil based on. */ private readonly _destroySubject = new Subject(); + constructor(...args: unknown[]); + constructor() {} + /** Completes the active subject, signalling to all other observables to complete. */ ngOnDestroy() { this._destroySubject.next(); diff --git a/src/cdk/layout/layout.md b/src/cdk/layout/layout.md index adbb2c711c55..888476bfc40a 100644 --- a/src/cdk/layout/layout.md +++ b/src/cdk/layout/layout.md @@ -79,7 +79,8 @@ The `matchMedia` method can be used to get a native ```ts @Component({...}) class MyComponent { - private mediaMatcher = inject(MediaMatcher); - private mediaQueryList = this.mediaMatcher.matchMedia('(min-width: 1px)'); + constructor(mediaMatcher: MediaMatcher) { + const mediaQueryList = mediaMatcher.matchMedia('(min-width: 1px)'); + } } ``` diff --git a/src/cdk/layout/media-matcher.md b/src/cdk/layout/media-matcher.md index 6dd2ba38dc75..6e071b5bd226 100644 --- a/src/cdk/layout/media-matcher.md +++ b/src/cdk/layout/media-matcher.md @@ -1,19 +1,17 @@ ### MediaMatcher - -`MediaMatcher` is an injectable service that provides access to the `matchMedia` method, if -available on the platform. + +When including the CDK's `LayoutModule`, components can inject `MediaMatcher` to access the +matchMedia method, if available on the platform. #### Example ```ts -@Component({ ... }) -export class MyWidget { - private mediaMatcher = inject(MediaMatcher); - - checkOrientation() { - this.mediaMatcher.matchMedia('(orientation: landscape)').matches ? +@Component({ ... }) +export class MyWidget { + constructor(mm: MediaMatcher) { + mm.matchMedia('(orientation: landscape)').matches ? this.setLandscapeMode() : this.setPortraitMode(); } -} +} ``` diff --git a/src/cdk/layout/media-matcher.ts b/src/cdk/layout/media-matcher.ts index cdce864b36e3..1dcad6ef72d0 100644 --- a/src/cdk/layout/media-matcher.ts +++ b/src/cdk/layout/media-matcher.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {Service, CSP_NONCE, inject} from '@angular/core'; +import {Injectable, CSP_NONCE, inject} from '@angular/core'; import {Platform} from '../platform'; /** Global registry for all dynamically-created, injected media queries. */ @@ -15,7 +15,7 @@ const mediaQueriesForWebkitCompatibility: Set = new Set(); let mediaQueryStyleNode: HTMLStyleElement | undefined; /** A utility for calling matchMedia queries. */ -@Service() +@Injectable({providedIn: 'root'}) export class MediaMatcher { private _platform = inject(Platform); private _nonce = inject(CSP_NONCE, {optional: true}); @@ -23,6 +23,8 @@ export class MediaMatcher { /** The internal matchMedia method to return back a MediaQueryList like object. */ private _matchMedia: (query: string) => MediaQueryList; + constructor(...args: unknown[]); + constructor() { this._matchMedia = this._platform.isBrowser && window.matchMedia @@ -73,12 +75,7 @@ function createEmptyStyleRule(query: string, nonce: string | undefined | null) { } if (mediaQueryStyleNode.sheet) { - mediaQueryStyleNode.sheet.insertRule( - // Drop the curly braces to avoid injection attacks. Curly braces aren't - // valid media query syntax so this should be a no-op in valid cases. - `@media ${query.replace(/[{}]/g, '')} {body{ }}`, - 0, - ); + mediaQueryStyleNode.sheet.insertRule(`@media ${query} {body{ }}`, 0); mediaQueriesForWebkitCompatibility.add(query); } } catch (e) { diff --git a/src/cdk/listbox/listbox.md b/src/cdk/listbox/listbox.md index 0be6b63f8f77..021b24a905aa 100644 --- a/src/cdk/listbox/listbox.md +++ b/src/cdk/listbox/listbox.md @@ -44,7 +44,7 @@ In addition to CSS classes, these directives add aria attributes that can be tar ### Getting started -Import `CdkListbox` and `CdkOption` in the component where you want to create a listbox. You can +Import the `CdkListboxModule` into the `NgModule` in which you want to create a listbox. You can then apply listbox directives to build your custom listbox. A typical listbox consists of the following directives: diff --git a/src/cdk/listbox/listbox.spec.ts b/src/cdk/listbox/listbox.spec.ts index e45f6796200c..393f9c3123d3 100644 --- a/src/cdk/listbox/listbox.spec.ts +++ b/src/cdk/listbox/listbox.spec.ts @@ -1,5 +1,5 @@ import {A, B, DOWN_ARROW, END, HOME, LEFT_ARROW, RIGHT_ARROW, SPACE, UP_ARROW} from '../keycodes'; -import {Component, Type, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Type, signal} from '@angular/core'; import {TestBed, fakeAsync, tick} from '@angular/core/testing'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; @@ -433,7 +433,7 @@ describe('CdkOption and CdkListbox', () => { expect(fixture.componentInstance.changedOption).toBe(null); }); - it('should not change selection on keyboard activation of a disabled option', () => { + it('should not change selection on click of a disabled option', () => { const {fixture, testComponent, listbox, listboxEl} = setupComponent(ListboxWithOptions); listbox.focus(); fixture.detectChanges(); @@ -983,7 +983,6 @@ describe('CdkOption and CdkListbox', () => { `, imports: [CdkListbox, CdkOption], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithOptions { changedOption: CdkOption | null = null; @@ -1010,7 +1009,6 @@ class ListboxWithOptions { @Component({ template: `
    `, imports: [CdkListbox], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithNoOptions {} @@ -1027,7 +1025,6 @@ class ListboxWithNoOptions {} `, imports: [CdkListbox, CdkOption, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithFormControl { formControl = new FormControl(); @@ -1044,7 +1041,6 @@ class ListboxWithFormControl { `, imports: [CdkListbox, CdkOption, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithPreselectedFormControl { options = ['a', 'b', 'c']; @@ -1060,7 +1056,6 @@ class ListboxWithPreselectedFormControl { `, imports: [CdkListbox, CdkOption, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithInvalidPreselectedFormControl { options = ['a', 'b', 'c']; @@ -1077,7 +1072,6 @@ class ListboxWithInvalidPreselectedFormControl { `, imports: [CdkListbox, CdkOption], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithCustomTypeahead {} @@ -1092,7 +1086,6 @@ class ListboxWithCustomTypeahead {} `, imports: [CdkListbox, CdkOption], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithBoundValue { value = ['banana']; @@ -1110,7 +1103,6 @@ class ListboxWithBoundValue { `, imports: [CdkListbox, CdkOption], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithMultipleBoundValues { value = ['apple', 'banana']; @@ -1125,7 +1117,6 @@ class ListboxWithMultipleBoundValues { `, imports: [CdkListbox, CdkOption], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListboxWithObjectValues { fruits = [{name: 'Apple'}, {name: 'Orange'}, {name: 'Banana'}, {name: 'Peach'}]; diff --git a/src/cdk/menu/context-menu-trigger.spec.ts b/src/cdk/menu/context-menu-trigger.spec.ts index 0ad146615df9..609bb5a92915 100644 --- a/src/cdk/menu/context-menu-trigger.spec.ts +++ b/src/cdk/menu/context-menu-trigger.spec.ts @@ -1,11 +1,4 @@ -import { - Component, - ViewChild, - ElementRef, - ViewChildren, - QueryList, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ViewChild, ElementRef, ViewChildren, QueryList} from '@angular/core'; import {TestBed, ComponentFixture} from '@angular/core/testing'; import {CdkMenu} from './menu'; import {CdkContextMenuTrigger} from './context-menu-trigger'; @@ -428,7 +421,6 @@ describe('CdkContextMenuTrigger', () => { `, imports: [CdkContextMenuTrigger, CdkMenu, CdkMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleContextMenu { @ViewChild(CdkContextMenuTrigger) trigger!: CdkContextMenuTrigger; @@ -458,7 +450,6 @@ class SimpleContextMenu { `, imports: [CdkContextMenuTrigger, CdkMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedContextMenu { @ViewChild('cut_trigger', {read: ElementRef}) cutContext!: ElementRef; @@ -485,7 +476,6 @@ class NestedContextMenu { `, imports: [CdkContextMenuTrigger, CdkMenuTrigger, CdkMenu, CdkMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class ContextMenuWithSubmenu { @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) context!: ElementRef; @@ -517,7 +507,6 @@ class ContextMenuWithSubmenu { `, imports: [CdkContextMenuTrigger, CdkMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class ContextMenuWithMenuBarAndInlineMenu { @ViewChild(CdkMenuBar, {read: ElementRef}) nativeMenuBar!: ElementRef; @@ -547,7 +536,6 @@ class ContextMenuWithMenuBarAndInlineMenu { `, imports: [CdkMenuBar, CdkContextMenuTrigger, CdkMenu, CdkMenuItem, CdkMenuTrigger], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuBarAndContextTriggerShareMenu { @ViewChild(CdkMenuTrigger) menuBarTrigger!: CdkMenuTrigger; @@ -564,7 +552,6 @@ class MenuBarAndContextTriggerShareMenu { `, imports: [CdkContextMenuTrigger, CdkMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class ContextTriggerWithData { @ViewChild(CdkContextMenuTrigger, {read: ElementRef}) triggerElement!: ElementRef; diff --git a/src/cdk/menu/context-menu-trigger.ts b/src/cdk/menu/context-menu-trigger.ts index 0b719aa33c61..bc6cac4f2798 100644 --- a/src/cdk/menu/context-menu-trigger.ts +++ b/src/cdk/menu/context-menu-trigger.ts @@ -38,6 +38,12 @@ const CONTEXT_MENU_POSITIONS = STANDARD_DROPDOWN_BELOW_POSITIONS.map(position => return {...position, offsetX, offsetY}; }); +/** + * @deprecated Will be removed. Use `MenuTracker` instead. + * @breaking-change 22.0.0 + */ +export {MenuTracker as ContextMenuTracker}; + /** The coordinates where the context menu should open. */ export type ContextMenuCoordinates = {x: number; y: number}; diff --git a/src/cdk/menu/menu-aim.ts b/src/cdk/menu/menu-aim.ts index 7e04e120cda6..2610532a247e 100644 --- a/src/cdk/menu/menu-aim.ts +++ b/src/cdk/menu/menu-aim.ts @@ -9,7 +9,7 @@ import { Directive, inject, - Service, + Injectable, InjectionToken, NgZone, OnDestroy, @@ -107,7 +107,7 @@ function isWithinSubmenu(submenuPoints: DOMRect, m: number, b: number) { * approach by checking if at least NUM_POINTS / 2 pairs determine that the user is moving towards * to submenu. */ -@Service({autoProvided: false}) +@Injectable() export class TargetMenuAim implements MenuAim, OnDestroy { private readonly _ngZone = inject(NgZone); private readonly _renderer = inject(RendererFactory2).createRenderer(null, null); diff --git a/src/cdk/menu/menu-bar.spec.ts b/src/cdk/menu/menu-bar.spec.ts index 3b47dbaa2978..a7017fe5b644 100644 --- a/src/cdk/menu/menu-bar.spec.ts +++ b/src/cdk/menu/menu-bar.spec.ts @@ -1,4 +1,4 @@ -import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import { Component, ElementRef, @@ -6,7 +6,6 @@ import { QueryList, ViewChild, ViewChildren, - ChangeDetectionStrategy, } from '@angular/core'; import {By} from '@angular/platform-browser'; import { @@ -149,15 +148,15 @@ describe('MenuBar', () => { expect(document.activeElement).toEqual(menuBarNativeItems[menuBarNativeItems.length - 1]); }); - it('should focus the edit MenuItem on E, D character keys', async () => { + it('should focus the edit MenuItem on E, D character keys', fakeAsync(() => { focusMenuBar(); dispatchKeyboardEvent(nativeMenuBar, 'keydown', E); dispatchKeyboardEvent(nativeMenuBar, 'keydown', D); - await wait(600); + tick(500); detectChanges(); expect(document.activeElement).toEqual(menuBarNativeItems[1]); - }); + })); it( 'should toggle and wrap when cycling the right/left arrow keys on menu bar ' + @@ -450,16 +449,16 @@ describe('MenuBar', () => { expect(nativeMenus.length).toBe(0); }); - it('should focus share MenuItem on S, H character key press', async () => { + it('should focus share MenuItem on S, H character key press', fakeAsync(() => { openFileMenu(); dispatchKeyboardEvent(nativeMenus[0], 'keydown', S); dispatchKeyboardEvent(nativeMenus[0], 'keydown', H); - await wait(600); + tick(500); detectChanges(); expect(document.activeElement).toEqual(fileMenuNativeItems[1]); - }); + })); it('should handle keyboard actions if initial menu is opened programmatically', () => { fixture.debugElement @@ -1062,10 +1061,6 @@ describe('MenuBar', () => { }); }); -function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - @Component({ template: `
      @@ -1082,7 +1077,6 @@ function wait(milliseconds: number) {
    `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuBarRadioGroup {} @@ -1118,7 +1112,6 @@ class MenuBarRadioGroup {} `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiMenuWithSubmenu { clickEmitter = new EventEmitter(); @@ -1145,7 +1138,6 @@ class MultiMenuWithSubmenu { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithCheckboxes { @ViewChild(CdkMenuBar, {read: ElementRef}) nativeMenuBar!: ElementRef; @@ -1173,7 +1165,6 @@ class MenuWithCheckboxes { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithRadioButtons { @ViewChild(CdkMenuBar, {read: ElementRef}) nativeMenuBar!: ElementRef; @@ -1207,7 +1198,6 @@ class MenuWithRadioButtons { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuBarWithMenusAndInlineMenu { @ViewChildren(CdkMenu) menus!: QueryList; diff --git a/src/cdk/menu/menu-group.spec.ts b/src/cdk/menu/menu-group.spec.ts index 73e3e9ef6232..2b41d725fe11 100644 --- a/src/cdk/menu/menu-group.spec.ts +++ b/src/cdk/menu/menu-group.spec.ts @@ -1,4 +1,4 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {CdkMenuModule} from './menu-module'; @@ -96,7 +96,6 @@ describe('MenuGroup', () => { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CheckboxMenu { @ViewChild(CdkMenuItem) readonly trigger!: CdkMenuItem; @@ -141,7 +140,6 @@ class CheckboxMenu { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithMultipleRadioGroups { @ViewChild(CdkMenuItem) readonly trigger!: CdkMenuItem; diff --git a/src/cdk/menu/menu-item-checkbox.spec.ts b/src/cdk/menu/menu-item-checkbox.spec.ts index e0a1f249e055..d35bb43976e3 100644 --- a/src/cdk/menu/menu-item-checkbox.spec.ts +++ b/src/cdk/menu/menu-item-checkbox.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {CdkMenuModule} from './menu-module'; @@ -90,6 +90,5 @@ describe('MenuItemCheckbox', () => { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SingleCheckboxButton {} diff --git a/src/cdk/menu/menu-item-radio.spec.ts b/src/cdk/menu/menu-item-radio.spec.ts index fed614abe45a..84fc0a845427 100644 --- a/src/cdk/menu/menu-item-radio.spec.ts +++ b/src/cdk/menu/menu-item-radio.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {CdkMenuModule} from './menu-module'; @@ -80,6 +80,5 @@ describe('MenuItemRadio', () => { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleRadioButton {} diff --git a/src/cdk/menu/menu-item.spec.ts b/src/cdk/menu/menu-item.spec.ts index a3564cf65c5e..4476fdc86d9f 100644 --- a/src/cdk/menu/menu-item.spec.ts +++ b/src/cdk/menu/menu-item.spec.ts @@ -1,4 +1,4 @@ -import {Component, Type, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Type} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {dispatchFakeEvent, dispatchKeyboardEvent} from '../testing/private'; import {By} from '@angular/platform-browser'; @@ -139,7 +139,6 @@ describe('MenuItem', () => { @Component({ selector: 'mat-icon', template: '', - changeDetection: ChangeDetectionStrategy.Eager, }) class FakeMatIcon {} @@ -150,7 +149,6 @@ class FakeMatIcon {} `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SingleMenuItem {} @@ -164,7 +162,6 @@ class SingleMenuItem {} `, imports: [CdkMenuModule, FakeMatIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuItemWithIcon { typeahead: string | null = null; @@ -180,7 +177,6 @@ class MenuItemWithIcon { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuItemWithIconClass { typeahead: string | null = null; @@ -193,7 +189,6 @@ class MenuItemWithIconClass { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuItemWithBoldElement {} @@ -213,7 +208,6 @@ class MenuItemWithBoldElement {} `, imports: [CdkMenuModule, FakeMatIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuItemWithMultipleNestings { typeahead = ''; diff --git a/src/cdk/menu/menu-stack.spec.ts b/src/cdk/menu/menu-stack.spec.ts index 8af6b3382cba..250ee37d250b 100644 --- a/src/cdk/menu/menu-stack.spec.ts +++ b/src/cdk/menu/menu-stack.spec.ts @@ -1,10 +1,4 @@ -import { - QueryList, - ViewChild, - ViewChildren, - Component, - ChangeDetectionStrategy, -} from '@angular/core'; +import {QueryList, ViewChild, ViewChildren, Component} from '@angular/core'; import {CdkMenu} from './menu'; import {CdkMenuBar} from './menu-bar'; import {ComponentFixture, TestBed} from '@angular/core/testing'; @@ -101,7 +95,6 @@ describe('MenuStack', () => { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiMenuWithSubmenu { @ViewChild(CdkMenuBar) menuBar!: CdkMenuBar; diff --git a/src/cdk/menu/menu-stack.ts b/src/cdk/menu/menu-stack.ts index 185f9f0de649..85c888e57bdd 100644 --- a/src/cdk/menu/menu-stack.ts +++ b/src/cdk/menu/menu-stack.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service, InjectionToken} from '@angular/core'; +import {inject, Injectable, InjectionToken} from '@angular/core'; import {_IdGenerator} from '../a11y'; import {Observable, Subject} from 'rxjs'; import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators'; @@ -64,7 +64,7 @@ export interface MenuStackCloseEvent { * from the `empty` observable specifying the next focus action which the listener should perform * as requested by the closer. */ -@Service({autoProvided: false}) +@Injectable() export class MenuStack { /** The ID of this menu stack. */ readonly id = inject(_IdGenerator).getId('cdk-menu-stack-'); diff --git a/src/cdk/menu/menu-trigger-base.ts b/src/cdk/menu/menu-trigger-base.ts index 1457c4d0e256..f9af04696c1f 100644 --- a/src/cdk/menu/menu-trigger-base.ts +++ b/src/cdk/menu/menu-trigger-base.ts @@ -10,7 +10,7 @@ import { Directive, EventEmitter, inject, - Service, + Injectable, InjectionToken, Injector, OnDestroy, @@ -44,7 +44,7 @@ export const MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( ); /** Tracks the last open menu trigger across the entire application. */ -@Service() +@Injectable({providedIn: 'root'}) export class MenuTracker { /** The last open menu trigger. */ private static _openMenuTrigger?: CdkMenuTriggerBase; diff --git a/src/cdk/menu/menu-trigger.spec.ts b/src/cdk/menu/menu-trigger.spec.ts index 4c249a9787df..a7dfe1649e19 100644 --- a/src/cdk/menu/menu-trigger.spec.ts +++ b/src/cdk/menu/menu-trigger.spec.ts @@ -1,13 +1,6 @@ import {ENTER, SPACE, TAB} from '../keycodes'; -import { - Component, - ElementRef, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ElementRef, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {dispatchKeyboardEvent} from '../../cdk/testing/private'; import {CdkMenu} from './menu'; @@ -522,19 +515,20 @@ describe('MenuTrigger', () => { }); }); - it('should focus the first item when opening on click', () => { + it('should focus the first item when opening on click', fakeAsync(() => { const fixture = TestBed.createComponent(TriggersWithSameMenuDifferentMenuBars); fixture.detectChanges(); fixture.componentInstance.nativeTriggers.first.nativeElement.click(); fixture.detectChanges(); + tick(); const firstItem = fixture.componentInstance.nativeMenus.first.nativeElement.querySelector('.cdk-menu-item'); expect(firstItem).toBeTruthy(); expect(document.activeElement).toBe(firstItem); - }); + })); }); @Component({ @@ -543,7 +537,6 @@ describe('MenuTrigger', () => {
    `, imports: [CdkMenuBar, CdkMenu, CdkMenuItem, CdkMenuTrigger], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggerForEmptyMenu { @ViewChild(CdkMenuTrigger) trigger!: CdkMenuTrigger; @@ -569,7 +562,6 @@ class TriggerForEmptyMenu { `, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger, CdkMenuBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuBarWithNestedSubMenus { @ViewChildren(CdkMenu) menus!: QueryList; @@ -597,7 +589,6 @@ class MenuBarWithNestedSubMenus { `, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger, CdkMenuBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggersWithSameMenuDifferentMenuBars { @ViewChildren(CdkMenuTrigger) triggers!: QueryList; @@ -621,7 +612,6 @@ class TriggersWithSameMenuDifferentMenuBars { `, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger, CdkMenuBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggersWithSameMenuSameMenuBar { @ViewChildren(CdkMenuTrigger) triggers!: QueryList; @@ -641,7 +631,6 @@ class TriggersWithSameMenuSameMenuBar { `, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger, CdkMenuBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggerOpensItsMenu { @ViewChildren(CdkMenuTrigger) triggers!: QueryList; @@ -667,7 +656,6 @@ class TriggerOpensItsMenu { `, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneTriggerWithInlineMenu { @ViewChild(CdkMenuItem, {read: ElementRef}) nativeTrigger!: ElementRef; @@ -688,7 +676,6 @@ class StandaloneTriggerWithInlineMenu { `, imports: [CdkMenu, CdkMenuTrigger], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggerWithData { menuData: unknown; @@ -699,7 +686,6 @@ class TriggerWithData { `, imports: [CdkMenuTrigger], - changeDetection: ChangeDetectionStrategy.Eager, }) class TriggerWithNullValue { @ViewChild(CdkMenuTrigger, {static: true}) diff --git a/src/cdk/menu/menu-trigger.ts b/src/cdk/menu/menu-trigger.ts index 151486bb4a62..9f7c63a642ed 100644 --- a/src/cdk/menu/menu-trigger.ts +++ b/src/cdk/menu/menu-trigger.ts @@ -145,7 +145,7 @@ export class CdkMenuTrigger extends CdkMenuTriggerBase implements OnChanges, OnD return this.childMenu; } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (changes['menuPosition'] && this.overlayRef) { this.overlayRef.updatePositionStrategy(this._getOverlayPositionStrategy()); } diff --git a/src/cdk/menu/menu.md b/src/cdk/menu/menu.md index f2c387ab4962..440614930ae3 100644 --- a/src/cdk/menu/menu.md +++ b/src/cdk/menu/menu.md @@ -41,8 +41,8 @@ The available CSS classes are listed below, by directive. ### Getting started -Import `CdkMenu`, `CdkMenuItem`, and `CdkMenuTrigger` in the component where you want to create -menus. You can then apply menu directives to build your custom menu. A typical menu consists of the following directives: +Import the `CdkMenuModule` into the `NgModule` in which you want to create menus. You can then apply +menu directives to build your custom menu. A typical menu consists of the following directives: - `cdkMenuTriggerFor` - links a trigger element to an `ng-template` containing the menu to be opened - `cdkMenu` - creates the menu content opened by the trigger diff --git a/src/cdk/menu/menu.spec.ts b/src/cdk/menu/menu.spec.ts index 8bfd26e24c95..b41fb1c0beb3 100644 --- a/src/cdk/menu/menu.spec.ts +++ b/src/cdk/menu/menu.spec.ts @@ -1,13 +1,13 @@ import {TAB} from '../keycodes'; +import {Component, ElementRef, QueryList, ViewChild, ViewChildren} from '@angular/core'; import { - Component, - ElementRef, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import { createMouseEvent, @@ -80,12 +80,6 @@ describe('Menu', () => { }); describe('menu aim', () => { - // TODO(crisbeto): update the component to clear timeouts on destroy. - // Give some time for timeouts to be cleaned up. - afterEach(async () => { - await wait(350); - }); - /** A coordinate in the browser window */ type Point = {x: number; y: number}; @@ -188,13 +182,13 @@ describe('Menu', () => { * * @return the number of elements the mouse entered into. */ - async function hover(from: Point, to: Point, inMenu: HTMLElement, duration: number) { + function hover(from: Point, to: Point, inMenu: HTMLElement, duration: number) { const getNextPoint = getNextPointIterator(from, to); let currentPoint: Point | null = from; let currElement = getElementAt(currentPoint); - const timeout = duration / Math.abs(to.x - from.x); + const timeout = duration / (to.x - from.x); let numEnters = 0; while (currentPoint) { @@ -208,9 +202,7 @@ describe('Menu', () => { fixture.detectChanges(); } currentPoint = getNextPoint(); - if (timeout > 0) { - await wait(timeout); - } + tick(timeout); } return numEnters; } @@ -232,31 +224,31 @@ describe('Menu', () => { }; } - it('should close the edit menu when hovering directly down from the edit menu trigger to the print item without waiting', async () => { + it('should close the edit menu when hovering directly down from the edit menu trigger to the print item without waiting', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const printPosition = nativeFileButtons![4].getBoundingClientRect(); - const numEnterEvents = await hover( + const numEnterEvents = hover( {x: editPosition.x, y: editPosition.y + 1}, {x: printPosition.x + 5, y: printPosition.y + 1}, nativeMenus[0], - 0, + 100, ); detectChanges(); expect(numEnterEvents).toBe(4); expect(nativeMenus.length).toBe(1); - }); + })); - it('should close the edit menu after moving towards submenu and stopping', async () => { + it('should close the edit menu after moving towards submenu and stopping', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const sharePosition = nativeShareTrigger!.getBoundingClientRect(); - const numEnters = await hover( + const numEnters = hover( { x: editPosition.x + editPosition.width / 2, y: editPosition.y + editPosition.height - 10, @@ -266,43 +258,44 @@ describe('Menu', () => { y: sharePosition.y + sharePosition.height - 10, }, nativeMenus[0], - 0, + 100, ); - await wait(2100); + tick(2000); detectChanges(); expect(numEnters).toBe(1); expect(nativeMenus.length).toBe(2); expect(nativeMenus[1].id).toBe('share_menu'); - }); + })); - it('should not close the edit submenu when hovering into its items in time', async () => { + it('should not close the edit submenu when hovering into its items in time', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const pastePosition = nativeEditButtons![4].getBoundingClientRect(); - const numEnters = await hover(editPosition, pastePosition, nativeMenus[0], 0); + const numEnters = hover(editPosition, pastePosition, nativeMenus[0], 100); detectChanges(); - await fixture.whenStable(); + flush(); expect(numEnters).toBeGreaterThan(2); expect(nativeMenus.length).toBe(2); expect(nativeMenus[1].id).toBe('edit_menu'); - }); + })); - it('should close the edit menu when hovering into its items slowly', async () => { + it('should close the edit menu when hovering into its items slowly', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const pastePosition = nativeEditButtons![4].getBoundingClientRect(); - const numEnters = await hover(editPosition, pastePosition, nativeMenus[0], 1100); + const numEnters = hover(editPosition, pastePosition, nativeMenus[0], 4000); detectChanges(); + flush(); expect(numEnters).toBeGreaterThan(2); expect(nativeMenus.length).toBe(1); - }); + })); }); describe('with rtl layout and menu at bottom of page moving up and left', () => { @@ -383,13 +376,13 @@ describe('Menu', () => { * * @return the number of elements the mouse entered into. */ - async function hover(from: Point, to: Point, inMenu: HTMLElement, duration: number) { + function hover(from: Point, to: Point, inMenu: HTMLElement, duration: number) { const getNextPoint = getNextPointIterator(from, to); let currPoint: Point | null = from; let currElement = getElementAt(currPoint); - const timeout = duration / Math.abs(to.x - from.x); + const timeout = duration / (to.x - from.x); let numEnters = 0; while (currPoint) { @@ -403,9 +396,7 @@ describe('Menu', () => { fixture.detectChanges(); } currPoint = getNextPoint(); - if (timeout) { - await wait(timeout); - } + tick(timeout); } return numEnters; } @@ -427,63 +418,66 @@ describe('Menu', () => { }; } - it('should close the edit menu when hovering directly up from the edit menu trigger to the print item without waiting', async () => { + it('should close the edit menu when hovering directly up from the edit menu trigger to the print item without waiting', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); + tick(); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const printPosition = nativeFileButtons![0].getBoundingClientRect(); - const numEnterEvents = await hover( + const numEnterEvents = hover( {x: editPosition.x + editPosition.width / 2, y: editPosition.y + 5}, {x: printPosition.x + 10, y: printPosition.y - 10}, nativeMenus[0], - 0, + 100, ); detectChanges(); + flush(); expect(numEnterEvents).toBe(4); expect(nativeMenus.length).toBe(1); - }); + })); - it('should close the edit menu after moving towards submenu and stopping', async () => { + it('should close the edit menu after moving towards submenu and stopping', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const sharePosition = nativeShareTrigger!.getBoundingClientRect(); - const numEnters = await hover( + const numEnters = hover( {x: editPosition.x + editPosition.width / 2, y: editPosition.y + 5}, { x: sharePosition.x + 10, y: sharePosition.y + 10, }, nativeMenus[0], - 0, + 100, ); - await wait(2100); + tick(2000); detectChanges(); expect(numEnters).toBe(1); expect(nativeMenus.length).toBe(2); expect(nativeMenus[1].id).toBe('share_menu'); - }); + })); - it('should not close the edit submenu when hovering into its items in time', async () => { + it('should not close the edit submenu when hovering into its items in time', fakeAsync(() => { openFileMenu(); openMenuOnHover(nativeEditTrigger!); + tick(); const editPosition = nativeEditTrigger!.getBoundingClientRect(); const undoPosition = nativeEditButtons![0].getBoundingClientRect(); - const numEnters = await hover(editPosition, undoPosition, nativeMenus[0], 0); + const numEnters = hover(editPosition, undoPosition, nativeMenus[0], 100); detectChanges(); - await fixture.whenStable(); + flush(); expect(numEnters).toBeGreaterThan(2); expect(nativeMenus.length).toBe(2); expect(nativeMenus[1].id).toBe('edit_menu'); - }); + })); }); }); @@ -517,10 +511,6 @@ describe('Menu', () => { }); }); -function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - @Component({ template: `
    @@ -538,7 +528,6 @@ function wait(milliseconds: number) { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuCheckboxGroup { @ViewChild(CdkMenuItem) readonly trigger!: CdkMenuItem; @@ -552,7 +541,6 @@ class MenuCheckboxGroup {
    `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InlineMenu {} @@ -606,7 +594,6 @@ class InlineMenu {} `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WithComplexNestedMenus { @ViewChild('file_trigger', {read: ElementRef}) nativeFileTrigger!: ElementRef; @@ -667,7 +654,6 @@ class WithComplexNestedMenus { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WithComplexNestedMenusOnBottom { @ViewChild('file_trigger', {read: ElementRef}) nativeFileTrigger!: ElementRef; @@ -687,7 +673,6 @@ class WithComplexNestedMenusOnBottom { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithActiveItem { @ViewChild(CdkMenu) menu!: CdkMenu; @@ -707,7 +692,6 @@ class MenuWithActiveItem { `, imports: [CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMenuDefinition { @ViewChild('root', {read: CdkMenu}) root!: CdkMenu; diff --git a/src/cdk/menu/pointer-focus-tracker.spec.ts b/src/cdk/menu/pointer-focus-tracker.spec.ts index c2167e3c64a9..6f0e35ce3222 100644 --- a/src/cdk/menu/pointer-focus-tracker.spec.ts +++ b/src/cdk/menu/pointer-focus-tracker.spec.ts @@ -6,7 +6,6 @@ import { AfterViewInit, inject, Renderer2, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {createMouseEvent, dispatchEvent} from '../../cdk/testing/private'; @@ -102,7 +101,6 @@ describe('FocusMouseManger', () => { @Component({ selector: 'wrapper', template: ``, - changeDetection: ChangeDetectionStrategy.Eager, }) class MockWrapper implements FocusableElement { readonly _elementRef = inject>(ElementRef); @@ -119,7 +117,6 @@ class MockWrapper implements FocusableElement { `, imports: [MockWrapper], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiElementWithConditionalComponent implements AfterViewInit { private _renderer = inject(Renderer2); diff --git a/src/cdk/observers/observe-content.spec.ts b/src/cdk/observers/observe-content.spec.ts index 3627e21f10f4..a23c72588cec 100644 --- a/src/cdk/observers/observe-content.spec.ts +++ b/src/cdk/observers/observe-content.spec.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick, waitForAsync} from '@angular/core/testing'; import {ContentObserver, MutationObserverFactory, ObserversModule} from './observe-content'; @@ -252,7 +252,6 @@ describe('ContentObserver injectable', () => { [cdkObserveContentDisabled]="disabled">{{text}} `, imports: [ObserversModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithTextContent { text = ''; @@ -263,7 +262,6 @@ class ComponentWithTextContent { @Component({ template: `
    {{text}}
    `, imports: [ObserversModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithChildTextContent { text = ''; @@ -273,7 +271,6 @@ class ComponentWithChildTextContent { @Component({ template: `
    {{text}}
    `, imports: [ObserversModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithDebouncedListener { debounce = 500; @@ -284,7 +281,6 @@ class ComponentWithDebouncedListener { @Component({ template: `
    {{text}}
    `, imports: [ObserversModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class UnobservedComponentWithTextContent { @ViewChild('contentEl') contentEl!: ElementRef; diff --git a/src/cdk/observers/observe-content.ts b/src/cdk/observers/observe-content.ts index 5e23b1aeac02..3fa0664a7aac 100644 --- a/src/cdk/observers/observe-content.ts +++ b/src/cdk/observers/observe-content.ts @@ -12,7 +12,7 @@ import { Directive, ElementRef, EventEmitter, - Service, + Injectable, Input, NgModule, NgZone, @@ -55,7 +55,7 @@ function shouldIgnoreRecord(record: MutationRecord) { * Factory that creates a new MutationObserver and allows us to stub it out in unit tests. * @docs-private */ -@Service() +@Injectable({providedIn: 'root'}) export class MutationObserverFactory { create(callback: MutationCallback): MutationObserver | null { return typeof MutationObserver === 'undefined' ? null : new MutationObserver(callback); @@ -63,7 +63,7 @@ export class MutationObserverFactory { } /** An injectable service that allows watching elements for changes to their content. */ -@Service() +@Injectable({providedIn: 'root'}) export class ContentObserver implements OnDestroy { private _mutationObserverFactory = inject(MutationObserverFactory); @@ -79,6 +79,9 @@ export class ContentObserver implements OnDestroy { private _ngZone = inject(NgZone); + constructor(...args: unknown[]); + constructor() {} + ngOnDestroy() { this._observedElements.forEach((_, element) => this._cleanupObserver(element)); } @@ -210,6 +213,9 @@ export class CdkObserveContent implements AfterContentInit, OnDestroy { private _currentSubscription: Subscription | null = null; + constructor(...args: unknown[]); + constructor() {} + ngAfterContentInit() { if (!this._currentSubscription && !this.disabled) { this._subscribe(); diff --git a/src/cdk/observers/private/shared-resize-observer.spec.ts b/src/cdk/observers/private/shared-resize-observer.spec.ts index 9b338cb0912a..3354a03bda61 100644 --- a/src/cdk/observers/private/shared-resize-observer.spec.ts +++ b/src/cdk/observers/private/shared-resize-observer.spec.ts @@ -1,5 +1,5 @@ import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; -import {Component, ElementRef, inject, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, inject, ViewChild} from '@angular/core'; import {SharedResizeObserver} from './shared-resize-observer'; describe('SharedResizeObserver', () => { @@ -135,7 +135,6 @@ describe('SharedResizeObserver', () => {
    `, - changeDetection: ChangeDetectionStrategy.Eager, }) export class TestComponent { @ViewChild('el1') el1!: ElementRef; diff --git a/src/cdk/observers/private/shared-resize-observer.ts b/src/cdk/observers/private/shared-resize-observer.ts index f4bc0e818c31..6b3016ff4748 100644 --- a/src/cdk/observers/private/shared-resize-observer.ts +++ b/src/cdk/observers/private/shared-resize-observer.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service, NgZone, OnDestroy, RendererFactory2} from '@angular/core'; +import {inject, Injectable, NgZone, OnDestroy, RendererFactory2} from '@angular/core'; import {Observable, Subject} from 'rxjs'; import {filter, shareReplay, takeUntil} from 'rxjs/operators'; @@ -94,7 +94,9 @@ class SingleBoxSharedResizeObserver { * later calls to `observe` with a different box type from influencing the events dispatched to * earlier calls. */ -@Service() +@Injectable({ + providedIn: 'root', +}) export class SharedResizeObserver implements OnDestroy { private _cleanupErrorListener: (() => void) | undefined; diff --git a/src/cdk/overlay/dispatchers/base-overlay-dispatcher.ts b/src/cdk/overlay/dispatchers/base-overlay-dispatcher.ts index 531f2f34cc35..4d56a65ef244 100644 --- a/src/cdk/overlay/dispatchers/base-overlay-dispatcher.ts +++ b/src/cdk/overlay/dispatchers/base-overlay-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy, inject, DOCUMENT} from '@angular/core'; +import {Injectable, OnDestroy, inject, DOCUMENT} from '@angular/core'; import type {OverlayRef} from '../overlay-ref'; import {Subject} from 'rxjs'; @@ -15,7 +15,7 @@ import {Subject} from 'rxjs'; * if any. It maintains a list of attached overlays to determine best suited overlay based * on event target and order of overlay opens. */ -@Service() +@Injectable({providedIn: 'root'}) export abstract class BaseOverlayDispatcher implements OnDestroy { /** Currently attached overlays in the order they were attached. */ _attachedOverlays: OverlayRef[] = []; @@ -23,6 +23,10 @@ export abstract class BaseOverlayDispatcher implements OnDestroy { protected _document = inject(DOCUMENT); protected _isAttached = false; + constructor(...args: unknown[]); + + constructor() {} + ngOnDestroy(): void { this.detach(); } diff --git a/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.spec.ts b/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.spec.ts index 49416fad520c..a5b89112a693 100644 --- a/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.spec.ts +++ b/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.spec.ts @@ -1,6 +1,6 @@ import {ESCAPE} from '../../keycodes'; import {ComponentPortal} from '../../portal'; -import {ApplicationRef, Component, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {ApplicationRef, Component, Injector} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {dispatchKeyboardEvent} from '../../testing/private'; import {createOverlayRef} from '../index'; @@ -17,7 +17,7 @@ describe('OverlayKeyboardDispatcher', () => { injector = TestBed.inject(Injector); }); - it('should track keyboard overlays in order as they are attached and detached', () => { + it('should track overlays in order as they are attached and detached', () => { const overlayOne = createOverlayRef(injector); const overlayTwo = createOverlayRef(injector); @@ -157,7 +157,7 @@ describe('OverlayKeyboardDispatcher', () => { expect(overlayOneSpy).toHaveBeenCalled(); }); - it('should not add the same keyboard overlay to the stack multiple times', () => { + it('should not add the same overlay to the stack multiple times', () => { const overlayOne = createOverlayRef(injector); const overlayTwo = createOverlayRef(injector); const overlayOneSpy = jasmine.createSpy('overlayOne keyboard event spy'); @@ -209,5 +209,5 @@ describe('OverlayKeyboardDispatcher', () => { }); }); -@Component({template: 'Hello', changeDetection: ChangeDetectionStrategy.Eager}) +@Component({template: 'Hello'}) class TestComponent {} diff --git a/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.ts b/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.ts index 2c767668582b..b3b0e6aff9d6 100644 --- a/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.ts +++ b/src/cdk/overlay/dispatchers/overlay-keyboard-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, RendererFactory2, inject} from '@angular/core'; +import {Injectable, NgZone, RendererFactory2, inject} from '@angular/core'; import {BaseOverlayDispatcher} from './base-overlay-dispatcher'; import type {OverlayRef} from '../overlay-ref'; @@ -15,7 +15,7 @@ import type {OverlayRef} from '../overlay-ref'; * if any. It maintains a list of attached overlays to determine best suited overlay based * on event target and order of overlay opens. */ -@Service() +@Injectable({providedIn: 'root'}) export class OverlayKeyboardDispatcher extends BaseOverlayDispatcher { private _ngZone = inject(NgZone); private _renderer = inject(RendererFactory2).createRenderer(null, null); diff --git a/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.spec.ts b/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.spec.ts index 622478c005b9..0ce18f25c739 100644 --- a/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.spec.ts +++ b/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.spec.ts @@ -1,4 +1,4 @@ -import {ApplicationRef, Component, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {ApplicationRef, Component, Injector} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {filter, take} from 'rxjs/operators'; import {ComponentPortal} from '../../portal'; @@ -17,7 +17,7 @@ describe('OverlayOutsideClickDispatcher', () => { injector = TestBed.inject(Injector); }); - it('should track outside click overlays in order as they are attached and detached', () => { + it('should track overlays in order as they are attached and detached', () => { const overlayOne = createOverlayRef(injector); const overlayTwo = createOverlayRef(injector); @@ -143,7 +143,7 @@ describe('OverlayOutsideClickDispatcher', () => { ); }); - it('should not add the same outside click overlay to the stack multiple times', () => { + it('should not add the same overlay to the stack multiple times', () => { const overlayOne = createOverlayRef(injector); const overlayTwo = createOverlayRef(injector); @@ -407,7 +407,7 @@ describe('OverlayOutsideClickDispatcher', () => { it('should run change detection if the click was made outside the overlay and there are `outsidePointerEvents` observers', async () => { let renders = 0; - @Component({template: '{{increment()}}', changeDetection: ChangeDetectionStrategy.Eager}) + @Component({template: '{{increment()}}'}) class Counter { increment() { renders++; @@ -448,5 +448,5 @@ describe('OverlayOutsideClickDispatcher', () => { }); }); -@Component({template: 'Hello', changeDetection: ChangeDetectionStrategy.Eager}) +@Component({template: 'Hello'}) class TestComponent {} diff --git a/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts b/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts index 89ecdcaac9d2..710a33438212 100644 --- a/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts +++ b/src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, RendererFactory2, inject} from '@angular/core'; +import {Injectable, NgZone, RendererFactory2, inject} from '@angular/core'; import {Platform, _getEventTarget} from '../../platform'; import {BaseOverlayDispatcher} from './base-overlay-dispatcher'; import type {OverlayRef} from '../overlay-ref'; @@ -16,7 +16,7 @@ import type {OverlayRef} from '../overlay-ref'; * if any. It maintains a list of attached overlays to determine best suited overlay based * on event target and order of overlay opens. */ -@Service() +@Injectable({providedIn: 'root'}) export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher { private _platform = inject(Platform); private _ngZone = inject(NgZone); diff --git a/src/cdk/overlay/fullscreen-overlay-container.spec.ts b/src/cdk/overlay/fullscreen-overlay-container.spec.ts index aae6c3205270..2622a904da87 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.spec.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.spec.ts @@ -1,13 +1,5 @@ import {waitForAsync, TestBed} from '@angular/core/testing'; -import { - Component, - ViewChild, - ViewContainerRef, - inject, - DOCUMENT, - Injector, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ViewChild, ViewContainerRef, inject, DOCUMENT, Injector} from '@angular/core'; import {CdkPortal} from '../portal'; import {OverlayContainer, FullscreenOverlayContainer, createOverlayRef} from './index'; @@ -115,7 +107,6 @@ describe('FullscreenOverlayContainer', () => { @Component({ template: `Cake`, imports: [CdkPortal], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestComponentWithTemplatePortals { viewContainerRef = inject(ViewContainerRef); diff --git a/src/cdk/overlay/fullscreen-overlay-container.ts b/src/cdk/overlay/fullscreen-overlay-container.ts index 87a73ddb1cdc..301e1a1eca29 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service, OnDestroy, RendererFactory2} from '@angular/core'; +import {inject, Injectable, OnDestroy, RendererFactory2} from '@angular/core'; import {OverlayContainer} from './overlay-container'; /** @@ -16,12 +16,18 @@ import {OverlayContainer} from './overlay-container'; * * Should be provided in the root component. */ -@Service() +@Injectable({providedIn: 'root'}) export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy { private _renderer = inject(RendererFactory2).createRenderer(null, null); private _fullScreenEventName: string | undefined; private _cleanupFullScreenListener: (() => void) | undefined; + constructor(...args: unknown[]); + + constructor() { + super(); + } + override ngOnDestroy() { super.ngOnDestroy(); this._cleanupFullScreenListener?.(); diff --git a/src/cdk/overlay/overlay-container.spec.ts b/src/cdk/overlay/overlay-container.spec.ts index c2863e175c6a..0ac658aa18ef 100644 --- a/src/cdk/overlay/overlay-container.spec.ts +++ b/src/cdk/overlay/overlay-container.spec.ts @@ -1,12 +1,5 @@ import {waitForAsync, TestBed} from '@angular/core/testing'; -import { - Component, - Injector, - ViewChild, - ViewContainerRef, - inject, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, Injector, ViewChild, ViewContainerRef, inject} from '@angular/core'; import {CdkPortal} from '../portal'; import {createOverlayRef, OverlayContainer} from './index'; @@ -90,7 +83,6 @@ describe('OverlayContainer', () => { @Component({ template: `Cake`, imports: [CdkPortal], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestComponentWithTemplatePortals { viewContainerRef = inject(ViewContainerRef); diff --git a/src/cdk/overlay/overlay-container.ts b/src/cdk/overlay/overlay-container.ts index 8ad92f1c7267..5b2f8a69f86c 100644 --- a/src/cdk/overlay/overlay-container.ts +++ b/src/cdk/overlay/overlay-container.ts @@ -6,12 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, OnDestroy, Component, ViewEncapsulation, inject, DOCUMENT} from '@angular/core'; +import { + Injectable, + OnDestroy, + Component, + ChangeDetectionStrategy, + ViewEncapsulation, + inject, + DOCUMENT, +} from '@angular/core'; import {_CdkPrivateStyleLoader} from '../private'; import {Platform, _isTestEnvironment} from '../platform'; @Component({ template: '', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styleUrl: 'overlay-structure.css', host: {'cdk-overlay-style-loader': ''}, @@ -19,7 +28,7 @@ import {Platform, _isTestEnvironment} from '../platform'; export class _CdkOverlayStyleLoader {} /** Container inside which all overlays will render. */ -@Service() +@Injectable({providedIn: 'root'}) export class OverlayContainer implements OnDestroy { protected _platform = inject(Platform); @@ -27,6 +36,9 @@ export class OverlayContainer implements OnDestroy { protected _document = inject(DOCUMENT); protected _styleLoader = inject(_CdkPrivateStyleLoader); + constructor(...args: unknown[]); + constructor() {} + ngOnDestroy() { this._containerElement?.remove(); } diff --git a/src/cdk/overlay/overlay-directives.spec.ts b/src/cdk/overlay/overlay-directives.spec.ts index 09c94ff22556..c704b29dc700 100644 --- a/src/cdk/overlay/overlay-directives.spec.ts +++ b/src/cdk/overlay/overlay-directives.spec.ts @@ -1,13 +1,5 @@ -import { - Component, - ElementRef, - Injector, - signal, - ViewChild, - WritableSignal, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ElementRef, Injector, signal, ViewChild, WritableSignal} from '@angular/core'; +import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {Subject} from 'rxjs'; import {Direction} from '../bidi'; import {A, ESCAPE} from '../keycodes'; @@ -178,7 +170,7 @@ describe('Overlay directives', () => { expect(event.defaultPrevented).toBe(false); }); - it('should prevent closing via clicks on the backdrop by default', () => { + it('should prevent closing via clicks on the backdrop by default', fakeAsync(() => { fixture.componentInstance.hasBackdrop = true; fixture.componentInstance.isOpen = true; fixture.changeDetectorRef.markForCheck(); @@ -189,7 +181,7 @@ describe('Overlay directives', () => { fixture.detectChanges(); expect(overlayContainerElement.textContent!.trim()).toBeTruthy(); - }); + })); it('should prevent closing via the escape key with disableClose option', () => { fixture.componentInstance.isOpen = true; @@ -204,7 +196,7 @@ describe('Overlay directives', () => { expect(event.defaultPrevented).toBe(false); }); - it('should not depend on the order in which the `origin` and `open` are set', () => { + it('should not depend on the order in which the `origin` and `open` are set', waitForAsync(() => { fixture.destroy(); const propOrderFixture = TestBed.createComponent(ConnectedOverlayPropertyInitOrder); @@ -217,7 +209,7 @@ describe('Overlay directives', () => { overlayDirective.origin = propOrderFixture.componentInstance.trigger; propOrderFixture.detectChanges(); }).not.toThrow(); - }); + })); describe('inputs', () => { it('should set the width', () => { @@ -321,7 +313,7 @@ describe('Overlay directives', () => { expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeNull(); }); - it('should be able to change hasBackdrop after the overlay has been initialized', async () => { + it('should be able to change hasBackdrop after the overlay has been initialized', fakeAsync(() => { // Open once with a backdrop fixture.componentInstance.hasBackdrop = true; fixture.componentInstance.isOpen = true; @@ -333,7 +325,7 @@ describe('Overlay directives', () => { fixture.componentInstance.isOpen = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await new Promise(resolve => setTimeout(resolve, 500)); + tick(500); // Open again without a backdrop. fixture.componentInstance.hasBackdrop = false; @@ -342,7 +334,7 @@ describe('Overlay directives', () => { fixture.detectChanges(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); - }); + })); it('should set the custom backdrop class', () => { fixture.componentInstance.hasBackdrop = true; @@ -529,7 +521,7 @@ describe('Overlay directives', () => { expect(Math.floor(overlayRect.left)).toBe(Math.floor(triggerRect.left) + 20); }); - it('should apply the panelClass from the position', () => { + it('should take the offset from the position', () => { fixture.componentInstance.positionOverrides = [ { originX: 'start', @@ -797,7 +789,6 @@ describe('Overlay directives', () => {

    Menu content

    `, imports: [OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedOverlayDirectiveTest { @ViewChild(CdkConnectedOverlay) connectedOverlayDirective!: CdkConnectedOverlay; @@ -841,7 +832,6 @@ class ConnectedOverlayDirectiveTest { Menu content `, imports: [OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedOverlayPropertyInitOrder { @ViewChild(CdkConnectedOverlay) connectedOverlayDirective!: CdkConnectedOverlay; diff --git a/src/cdk/overlay/overlay-directives.ts b/src/cdk/overlay/overlay-directives.ts index dba42a15754b..77d671fba1b7 100644 --- a/src/cdk/overlay/overlay-directives.ts +++ b/src/cdk/overlay/overlay-directives.ts @@ -92,6 +92,9 @@ export const CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY = new InjectionToken<() => Sc }) export class CdkOverlayOrigin { elementRef = inject(ElementRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -283,6 +286,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { /** Emits when there are mouse outside click events that are targeted at the overlay. */ @Output() readonly overlayOutsideClick = new EventEmitter(); + constructor(...args: unknown[]); + // TODO(jelbourn): inputs for size, scroll behavior, animation, etc. constructor() { @@ -318,7 +323,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { this._overlayRef?.dispose(); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (this._position) { this._updatePositionStrategy(this._position); this._overlayRef?.updateSize({ diff --git a/src/cdk/overlay/overlay-directives.zone.spec.ts b/src/cdk/overlay/overlay-directives.zone.spec.ts index e08e9f6268f9..8c2e792aed2f 100644 --- a/src/cdk/overlay/overlay-directives.zone.spec.ts +++ b/src/cdk/overlay/overlay-directives.zone.spec.ts @@ -1,11 +1,4 @@ -import { - Component, - ElementRef, - NgZone, - ViewChild, - provideZoneChangeDetection, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ElementRef, NgZone, ViewChild, provideZoneChangeDetection} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {CdkConnectedOverlay, CdkOverlayOrigin} from './overlay-directives'; import {OverlayModule} from './overlay-module'; @@ -82,7 +75,6 @@ describe('Overlay directives Zone.js integration', () => {

    Menu content

    `, imports: [OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ConnectedOverlayDirectiveTest { @ViewChild(CdkConnectedOverlay) connectedOverlayDirective!: CdkConnectedOverlay; diff --git a/src/cdk/overlay/overlay.md b/src/cdk/overlay/overlay.md index 1d0863b47669..8030aaa6b856 100644 --- a/src/cdk/overlay/overlay.md +++ b/src/cdk/overlay/overlay.md @@ -89,66 +89,19 @@ on how scroll events are detected and dispatched. ### The overlay container The `OverlayContainer` provides a handle to the container element in which all individual overlay -elements are rendered. By default, the overlay container is appended directly to the document body. - -In supported browsers, the CDK renders overlays as native -[Popover](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) elements. -The `OverlayContainer` acts as the default insertion point for these elements, but because they are -rendered as popovers, the browser automatically promotes them to the top layer. This natively -bypasses `z-index` and `overflow: hidden` clipping issues without needing complex DOM stacking -workarounds. - -If a browser does not support the Popover API, the CDK falls back to the traditional -behavior where appending to the `OverlayContainer` is the only way to avoid clipping. - -You can explicitly configure the popover behavior using the `usePopover` option when creating an -overlay. If you need to globally disable the popover behavior, you can provide the -`OVERLAY_DEFAULT_CONFIG` injection token: - -```ts -import {OVERLAY_DEFAULT_CONFIG} from '@angular/cdk/overlay'; - -bootstrapApplication(MyApp, { - providers: [ - {provide: OVERLAY_DEFAULT_CONFIG, useValue: {usePopover: false}} - ] -}); -``` - -#### Popover DOM location -When an overlay is rendered as a native popover, it doesn't strictly need to be placed inside the -`OverlayContainer` to escape `overflow: hidden` containers. The `FlexibleConnectedPositionStrategy` -allows you to change where the popover is inserted in the DOM via the `withPopoverLocation()` method. - -This is particularly beneficial for accessibility. By injecting the popover directly next to its -trigger element in the DOM structure, the DOM sequence naturally follows the visual logical order, -while the browser continues to render the popover on top of everything. - -If you are using the `cdkConnectedOverlay` directive, you can configure this using the -`cdkConnectedOverlayUsePopover` input. The supported locations are: -- `'global'` (default): Inserts the popover inside the `OverlayContainer`. -- `'inline'`: Inserts the popover in the DOM immediately following its trigger element. -- `{type: 'parent', element: HTMLElement}`: Inserts the popover as a child of a specific custom element. - -```html - - - - - -``` +elements are rendered. By default, the overlay container is appended directly to the document body +so that an overlay is never clipped by an `overflow: hidden` parent. #### Full-screen overlays The `FullscreenOverlayContainer` is an alternative to `OverlayContainer` that supports correct displaying of overlay elements in [fullscreen mode](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen). -`FullscreenOverlayContainer` can be enabled by providing it in your app config: +`FullscreenOverlayContainer` can be enabled by providing it in your `NgModule`: ```ts -bootstrapApplication(MyApp, { - providers: [{provide: OverlayContainer, useClass: FullscreenOverlayContainer}] -}); +@NgModule({ + providers: [{provide: OverlayContainer, useClass: FullscreenOverlayContainer}], + // ... +}) +export class MyModule { } ``` diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index a6eb23b80a8a..5fe8399135ec 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -5,7 +5,7 @@ import { Component, ErrorHandler, EventEmitter, - Service, + Injectable, Injector, Provider, ViewChild, @@ -13,9 +13,15 @@ import { WritableSignal, inject, signal, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {Direction, Directionality} from '../bidi'; import {CdkPortal, ComponentPortal, TemplatePortal} from '../portal'; import {dispatchFakeEvent} from '../testing/private'; @@ -70,11 +76,7 @@ describe('Overlay', () => { overlayContainer.ngOnDestroy(); } - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - beforeEach(setup); + beforeEach(waitForAsync(setup)); afterEach(cleanup); it('should load a component into an overlay', () => { @@ -296,7 +298,7 @@ describe('Overlay', () => { expect(overlayRef.getConfig().direction).toBe('ltr'); }); - it('should clear out all DOM element references on dispose', async () => { + it('should clear out all DOM element references on dispose', fakeAsync(() => { const overlayRef = createOverlayRef(injector, {hasBackdrop: true}); overlayRef.attach(componentPortal); @@ -309,7 +311,7 @@ describe('Overlay', () => { .toBeTruthy(); overlayRef.dispose(); - await wait(500); + tick(500); expect(overlayRef.hostElement) .withContext('Expected overlay host not to be referenced.') @@ -320,11 +322,34 @@ describe('Overlay', () => { expect(overlayRef.backdropElement) .withContext('Expected backdrop element not to be referenced.') .toBeFalsy(); - }); + })); + + it('should clear the backdrop timeout if the transition finishes first', fakeAsync(() => { + const overlayRef = createOverlayRef(injector, {hasBackdrop: true}); + + overlayRef.attach(componentPortal); + overlayRef.detach(); + + const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop')!; + dispatchFakeEvent(backdrop, 'transitionend'); + + // Note: we don't `tick` or `flush` here. The assertion is that + // `fakeAsync` will throw if we have an unflushed timer. + })); + + it('should clear the backdrop timeout if the overlay is disposed', fakeAsync(() => { + const overlayRef = createOverlayRef(injector, {hasBackdrop: true}); + overlayRef.attach(componentPortal); + overlayRef.detach(); + overlayRef.dispose(); + + // Note: we don't `tick` or `flush` here. The assertion is that + // `fakeAsync` will throw if we have an unflushed timer. + })); it('should be able to use the `Overlay` provider during app initialization', () => { /** Dummy provider that depends on `Overlay`. */ - @Service({autoProvided: false}) + @Injectable() class CustomErrorHandler extends ErrorHandler { private _overlay = inject(Overlay); @@ -440,7 +465,7 @@ describe('Overlay', () => { expect(() => overlayRef.removePanelClass([''])).not.toThrow(); }); - it('should detach a component-based overlay when the view is destroyed', async () => { + it('should detach a component-based overlay when the view is destroyed', fakeAsync(() => { const overlayRef = createOverlayRef(injector); const paneElement = overlayRef.overlayElement; @@ -450,26 +475,25 @@ describe('Overlay', () => { expect(paneElement.childNodes.length).not.toBe(0); viewContainerFixture.destroy(); - await viewContainerFixture.whenStable(); + flush(); expect(paneElement.childNodes.length).toBe(0); - }); + })); - it('should detach a template-based overlay when the view is destroyed', async () => { + it('should detach a template-based overlay when the view is destroyed', fakeAsync(() => { const overlayRef = createOverlayRef(injector); const paneElement = overlayRef.overlayElement; overlayRef.attach(templatePortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); expect(paneElement.childNodes.length).not.toBe(0); viewContainerFixture.destroy(); - await wait(100); + flush(); expect(paneElement.childNodes.length).toBe(0); - }); + })); it('should do nothing when trying to attach a disposed overlay', () => { const overlayRef = createOverlayRef(injector); @@ -485,17 +509,17 @@ describe('Overlay', () => { config = new OverlayConfig(); }); - it('should apply the positioning strategy', async () => { + it('should apply the positioning strategy', fakeAsync(() => { config.positionStrategy = new FakePositionStrategy(); createOverlayRef(injector, config).attach(componentPortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(overlayContainerElement.querySelectorAll('.fake-positioned').length).toBe(1); - }); + })); - it('should have the overlay in the DOM in position strategy when reattaching', async () => { + it('should have the overlay in the DOM in position strategy when reattaching', fakeAsync(() => { let overlayPresentInDom = false; config.positionStrategy = { @@ -512,17 +536,17 @@ describe('Overlay', () => { .toBeTruthy(); overlayRef.detach(); - await wait(0); + tick(); overlayRef.attach(componentPortal); - await wait(0); + tick(); expect(overlayPresentInDom) .withContext('Expected host element to be attached to the DOM.') .toBeTruthy(); - }); + })); - it('should not apply the position if it detaches before the zone stabilizes', async () => { + it('should not apply the position if it detaches before the zone stabilizes', fakeAsync(() => { config.positionStrategy = new FakePositionStrategy(); const overlayRef = createOverlayRef(injector, config); @@ -532,12 +556,12 @@ describe('Overlay', () => { overlayRef.attach(componentPortal); overlayRef.detach(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(config.positionStrategy.apply).not.toHaveBeenCalled(); - }); + })); - it('should be able to swap position strategies', async () => { + it('should be able to swap position strategies', fakeAsync(() => { const firstStrategy = new FakePositionStrategy(); const secondStrategy = new FakePositionStrategy(); @@ -552,7 +576,7 @@ describe('Overlay', () => { const overlayRef = createOverlayRef(injector, config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(firstStrategy.attach).toHaveBeenCalledTimes(1); expect(firstStrategy.apply).toHaveBeenCalledTimes(1); @@ -562,7 +586,7 @@ describe('Overlay', () => { overlayRef.updatePositionStrategy(secondStrategy); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(firstStrategy.attach).toHaveBeenCalledTimes(1); expect(firstStrategy.apply).toHaveBeenCalledTimes(1); @@ -570,9 +594,9 @@ describe('Overlay', () => { expect(secondStrategy.attach).toHaveBeenCalledTimes(1); expect(secondStrategy.apply).toHaveBeenCalledTimes(1); - }); + })); - it('should not do anything when trying to swap a positioning strategy with itself', async () => { + it('should not do anything when trying to swap a strategy with itself', fakeAsync(() => { const strategy = new FakePositionStrategy(); spyOn(strategy, 'attach'); @@ -584,7 +608,7 @@ describe('Overlay', () => { const overlayRef = createOverlayRef(injector, config); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(strategy.attach).toHaveBeenCalledTimes(1); expect(strategy.apply).toHaveBeenCalledTimes(1); @@ -592,12 +616,12 @@ describe('Overlay', () => { overlayRef.updatePositionStrategy(strategy); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(strategy.attach).toHaveBeenCalledTimes(1); expect(strategy.apply).toHaveBeenCalledTimes(1); expect(strategy.dispose).not.toHaveBeenCalled(); - }); + })); it('should not throw when disposing multiple times in a row', () => { const overlayRef = createOverlayRef(injector); @@ -611,6 +635,15 @@ describe('Overlay', () => { overlayRef.dispose(); }).not.toThrow(); }); + + it('should not trigger timers when disposing of an overlay', fakeAsync(() => { + const overlayRef = createOverlayRef(injector, {hasBackdrop: true}); + overlayRef.attach(templatePortal); + overlayRef.dispose(); + + // The assertion here is that `fakeAsync` doesn't flag + // any pending timeouts after the test is done. + })); }); describe('size', () => { @@ -1016,7 +1049,7 @@ describe('Overlay', () => { expect(fakeScrollStrategy.overlayRef).toBeNull(); }); - it('should be able to swap scroll strategies', async () => { + it('should be able to swap scroll strategies', fakeAsync(() => { const firstStrategy = new FakeScrollStrategy(); const secondStrategy = new FakeScrollStrategy(); @@ -1031,7 +1064,7 @@ describe('Overlay', () => { overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(firstStrategy.attach).toHaveBeenCalledTimes(1); expect(firstStrategy.enable).toHaveBeenCalledTimes(1); @@ -1041,7 +1074,7 @@ describe('Overlay', () => { overlayRef.updateScrollStrategy(secondStrategy); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(firstStrategy.attach).toHaveBeenCalledTimes(1); expect(firstStrategy.enable).toHaveBeenCalledTimes(1); @@ -1050,9 +1083,9 @@ describe('Overlay', () => { expect(secondStrategy.attach).toHaveBeenCalledTimes(1); expect(secondStrategy.enable).toHaveBeenCalledTimes(1); - }); + })); - it('should not do anything when trying to swap a scroll strategy with itself', async () => { + it('should not do anything when trying to swap a strategy with itself', fakeAsync(() => { const strategy = new FakeScrollStrategy(); spyOn(strategy, 'attach'); @@ -1064,7 +1097,7 @@ describe('Overlay', () => { overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(strategy.attach).toHaveBeenCalledTimes(1); expect(strategy.enable).toHaveBeenCalledTimes(1); @@ -1073,13 +1106,13 @@ describe('Overlay', () => { overlayRef.updateScrollStrategy(strategy); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); expect(strategy.attach).toHaveBeenCalledTimes(1); expect(strategy.enable).toHaveBeenCalledTimes(1); expect(strategy.disable).not.toHaveBeenCalled(); expect(strategy.detach).not.toHaveBeenCalled(); - }); + })); }); }); @@ -1087,7 +1120,6 @@ describe('Overlay', () => { @Component({ selector: 'pizza', template: '

    Pizza

    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg {} @@ -1095,7 +1127,6 @@ class PizzaMsg {} @Component({ template: `Cake`, imports: [CdkPortal], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestComponentWithTemplatePortals { viewContainerRef = inject(ViewContainerRef); diff --git a/src/cdk/overlay/overlay.ts b/src/cdk/overlay/overlay.ts index d868cc1320e8..ef13c0013395 100644 --- a/src/cdk/overlay/overlay.ts +++ b/src/cdk/overlay/overlay.ts @@ -11,7 +11,7 @@ import {DomPortalOutlet} from '../portal'; import {Location} from '@angular/common'; import { ApplicationRef, - Service, + Injectable, Injector, NgZone, ANIMATION_MODULE_TYPE, @@ -34,7 +34,6 @@ import {ScrollStrategyOptions} from './scroll/index'; /** Object used to configure the default options for overlays. */ export interface OverlayDefaultConfig { - /** Whether overlays should be rendered inside popovers by default. */ usePopover?: boolean; } @@ -69,10 +68,7 @@ export function createOverlayRef(injector: Injector, config?: OverlayConfig): Ov overlayConfig.direction = overlayConfig.direction || directionality.value; - // `document.body` can be null during page navigation or unload cycles per the WHATWG spec - // (https://html.spec.whatwg.org/multipage/dom.html#dom-document-body), even though TypeScript - // types it as non-nullable. Guard against it to avoid "Cannot use 'in' operator ... in null". - if (!doc.body || !('showPopover' in doc.body)) { + if (!('showPopover' in doc.body)) { overlayConfig.usePopover = false; } else { overlayConfig.usePopover = config?.usePopover ?? defaultUsePopover; @@ -126,12 +122,15 @@ export function createOverlayRef(injector: Injector, config?: OverlayConfig): Ov * * An overlay *is* a PortalOutlet, so any kind of Portal can be loaded into one. */ -@Service() +@Injectable({providedIn: 'root'}) export class Overlay { scrollStrategies = inject(ScrollStrategyOptions); private _positionBuilder = inject(OverlayPositionBuilder); private _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() {} + /** * Creates an overlay. * @param config Configuration applied to the overlay. diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts index 178297415e5d..655f01bcb9aa 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts @@ -9,9 +9,8 @@ import { Renderer2, RendererFactory2, runInInjectionContext, - ChangeDetectionStrategy, } from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {fakeAsync, TestBed, tick} from '@angular/core/testing'; import {Subscription} from 'rxjs'; import {map} from 'rxjs/operators'; import { @@ -2457,7 +2456,7 @@ describe('FlexibleConnectedPositionStrategy', () => { }, ); - it('should account for sub-pixel deviations in the size of the overlay', async () => { + it('should account for sub-pixel deviations in the size of the overlay', fakeAsync(() => { originElement.style.top = '200px'; originElement.style.left = '200px'; @@ -2497,12 +2496,11 @@ describe('FlexibleConnectedPositionStrategy', () => { // Trigger a resize so that the overlay get repositioned from scratch // and to have it use the patched `getBoundingClientRect`. dispatchFakeEvent(window, 'resize'); - // The resize listener is usually debounced. - await new Promise(resolve => setTimeout(resolve, 200)); + tick(100); // The resize listener is usually debounced. const overlayRect = originalGetBoundingClientRect.apply(overlayRef.overlayElement); expect(Math.floor(overlayRect.top)).toBe(0); - }); + })); }); describe('onPositionChange with scrollable view properties', () => { @@ -3075,6 +3073,5 @@ function createOverflowContainerElement() { style="width: ${DEFAULT_WIDTH}px; height: ${DEFAULT_HEIGHT}px;"> `, imports: [ScrollingModule, OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestOverlay {} diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index 897e205915dc..3f1c84631fc6 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -8,7 +8,7 @@ import {PositionStrategy} from './position-strategy'; import {DOCUMENT, ElementRef, Injector} from '@angular/core'; -import {ViewportRuler, ScrollDispatcherTarget, ViewportScrollPosition} from '../../scrolling'; +import {ViewportRuler, CdkScrollable, ViewportScrollPosition} from '../../scrolling'; import { ConnectedOverlayPositionChange, ConnectionPositionPair, @@ -117,7 +117,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { private _viewportMargin: ViewportMargin = 0; /** The Scrollable containers used to check scrollable view properties on position change. */ - private _scrollables: ScrollDispatcherTarget[] = []; + private _scrollables: CdkScrollable[] = []; /** Ordered list of preferred positions, from most to least desirable. */ _preferredPositions: ConnectionPositionPair[] = []; @@ -416,7 +416,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { * on reposition we can evaluate if it or the overlay has been clipped or outside view. Every * Scrollable must be an ancestor element of the strategy's origin element. */ - withScrollableContainers(scrollables: ScrollDispatcherTarget[]): this { + withScrollableContainers(scrollables: CdkScrollable[]): this { this._scrollables = scrollables; return this; } diff --git a/src/cdk/overlay/position/global-position-strategy.spec.ts b/src/cdk/overlay/position/global-position-strategy.spec.ts index 1cc156cc2baa..9d83675b063f 100644 --- a/src/cdk/overlay/position/global-position-strategy.spec.ts +++ b/src/cdk/overlay/position/global-position-strategy.spec.ts @@ -1,4 +1,4 @@ -import {Component, ApplicationRef, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ApplicationRef, Injector} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {PortalModule, ComponentPortal} from '../../portal'; import { @@ -475,6 +475,5 @@ describe('GlobalPositonStrategy', () => { @Component({ template: '', imports: [OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BlankPortal {} diff --git a/src/cdk/overlay/position/overlay-position-builder.ts b/src/cdk/overlay/position/overlay-position-builder.ts index a4c35a4293cb..671bbe2953da 100644 --- a/src/cdk/overlay/position/overlay-position-builder.ts +++ b/src/cdk/overlay/position/overlay-position-builder.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, Injector, inject} from '@angular/core'; +import {Injectable, Injector, inject} from '@angular/core'; import { createFlexibleConnectedPositionStrategy, FlexibleConnectedPositionStrategy, @@ -15,10 +15,13 @@ import { import {createGlobalPositionStrategy, GlobalPositionStrategy} from './global-position-strategy'; /** Builder for overlay position strategy. */ -@Service() +@Injectable({providedIn: 'root'}) export class OverlayPositionBuilder { private _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() {} + /** * Creates a global position strategy. */ diff --git a/src/cdk/overlay/scroll/block-scroll-strategy.spec.ts b/src/cdk/overlay/scroll/block-scroll-strategy.spec.ts index e305ac25a8b1..fdc60bcbe89f 100644 --- a/src/cdk/overlay/scroll/block-scroll-strategy.spec.ts +++ b/src/cdk/overlay/scroll/block-scroll-strategy.spec.ts @@ -1,4 +1,4 @@ -import {Component, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Injector} from '@angular/core'; import {waitForAsync, TestBed} from '@angular/core/testing'; import {ComponentPortal, PortalModule} from '../../portal'; import {Platform} from '../../platform'; @@ -264,6 +264,5 @@ describe('BlockScrollStrategy', () => { @Component({ template: '

    Focaccia

    ', imports: [OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FocacciaMsg {} diff --git a/src/cdk/overlay/scroll/close-scroll-strategy.spec.ts b/src/cdk/overlay/scroll/close-scroll-strategy.spec.ts index 3f868da013b3..5fe2444268ba 100644 --- a/src/cdk/overlay/scroll/close-scroll-strategy.spec.ts +++ b/src/cdk/overlay/scroll/close-scroll-strategy.spec.ts @@ -1,7 +1,7 @@ import {ComponentPortal, PortalModule} from '../../portal'; import {CdkScrollable, ScrollDispatcher, ViewportRuler} from '../../scrolling'; -import {Component, ElementRef, Injector, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ElementRef, Injector} from '@angular/core'; +import {TestBed, fakeAsync} from '@angular/core/testing'; import {Subject} from 'rxjs'; import { createCloseScrollStrategy, @@ -20,7 +20,7 @@ describe('CloseScrollStrategy', () => { let overlayContainer: OverlayContainer; let injector: Injector; - beforeEach(() => { + beforeEach(fakeAsync(() => { scrollPosition = 0; TestBed.configureTestingModule({ @@ -45,7 +45,7 @@ describe('CloseScrollStrategy', () => { const overlayConfig = new OverlayConfig({scrollStrategy: createCloseScrollStrategy(injector)}); overlayRef = createOverlayRef(injector, overlayConfig); componentPortal = new ComponentPortal(MozarellaMsg); - }); + })); afterEach(() => { overlayRef.dispose(); @@ -138,6 +138,5 @@ describe('CloseScrollStrategy', () => { @Component({ template: '

    Mozarella

    ', imports: [OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MozarellaMsg {} diff --git a/src/cdk/overlay/scroll/close-scroll-strategy.zone.spec.ts b/src/cdk/overlay/scroll/close-scroll-strategy.zone.spec.ts index 22b0520d0194..3fd78945b815 100644 --- a/src/cdk/overlay/scroll/close-scroll-strategy.zone.spec.ts +++ b/src/cdk/overlay/scroll/close-scroll-strategy.zone.spec.ts @@ -1,12 +1,6 @@ import {ComponentPortal, PortalModule} from '../../portal'; -import { - Component, - Injector, - NgZone, - provideZoneChangeDetection, - ChangeDetectionStrategy, -} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, Injector, NgZone, provideZoneChangeDetection} from '@angular/core'; +import {TestBed, fakeAsync} from '@angular/core/testing'; import {Subject} from 'rxjs'; import {OverlayConfig} from '../overlay-config'; import {OverlayContainer} from '../overlay-container'; @@ -27,7 +21,7 @@ describe('CloseScrollStrategy Zone.js integration', () => { let scrollPosition: number; let overlayContainer: OverlayContainer; - beforeEach(() => { + beforeEach(fakeAsync(() => { scrollPosition = 0; TestBed.configureTestingModule({ @@ -53,7 +47,7 @@ describe('CloseScrollStrategy Zone.js integration', () => { overlayRef = createOverlayRef(injector, overlayConfig); componentPortal = new ComponentPortal(MozarellaMsg); overlayContainer = TestBed.inject(OverlayContainer); - }); + })); afterEach(() => { overlayRef.dispose(); @@ -76,6 +70,5 @@ describe('CloseScrollStrategy Zone.js integration', () => { @Component({ template: '

    Mozarella

    ', imports: [OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MozarellaMsg {} diff --git a/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts b/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts index ec37583f6e98..f822c91971b3 100644 --- a/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts +++ b/src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts @@ -1,5 +1,5 @@ import {waitForAsync, TestBed} from '@angular/core/testing'; -import {Component, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Injector} from '@angular/core'; import {Subject} from 'rxjs'; import {ComponentPortal, PortalModule} from '../../portal'; import { @@ -116,6 +116,5 @@ describe('RepositionScrollStrategy', () => { @Component({ template: '

    Pasta

    ', imports: [OverlayModule, PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PastaMsg {} diff --git a/src/cdk/overlay/scroll/scroll-strategy-options.ts b/src/cdk/overlay/scroll/scroll-strategy-options.ts index 7ba84e48845b..d57971387339 100644 --- a/src/cdk/overlay/scroll/scroll-strategy-options.ts +++ b/src/cdk/overlay/scroll/scroll-strategy-options.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, Injector, inject} from '@angular/core'; +import {Injectable, Injector, inject} from '@angular/core'; import {createBlockScrollStrategy} from './block-scroll-strategy'; import {CloseScrollStrategyConfig, createCloseScrollStrategy} from './close-scroll-strategy'; import {NoopScrollStrategy} from './noop-scroll-strategy'; @@ -21,10 +21,13 @@ import { * Users can provide a custom value for `ScrollStrategyOptions` to replace the default * behaviors. This class primarily acts as a factory for ScrollStrategy instances. */ -@Service() +@Injectable({providedIn: 'root'}) export class ScrollStrategyOptions { private _injector = inject(Injector); + constructor(...args: unknown[]); + constructor() {} + /** Do nothing on scroll. */ noop = () => new NoopScrollStrategy(); diff --git a/src/cdk/platform/features/shadow-dom.ts b/src/cdk/platform/features/shadow-dom.ts index f65450f55681..6c0720f6d69f 100644 --- a/src/cdk/platform/features/shadow-dom.ts +++ b/src/cdk/platform/features/shadow-dom.ts @@ -57,14 +57,7 @@ export function _getFocusedElementPierceShadowDom(): HTMLElement | null { /** Gets the target of an event while accounting for Shadow DOM. */ export function _getEventTarget(event: Event): T | null { - // If an event is bound outside the Shadow DOM, the `event.target` will point to the shadow root - // so we have to use `composedPath` instead. Note that `composedPath` can throw if it's called - // during event replay (see #33386). - // TODO(crisbeto): it seems like `preventDefault` throws during replay as well. - if (event.composedPath) { - try { - return event.composedPath()[0] as T | null; - } catch {} - } - return event.target as T | null; + // If an event is bound outside the Shadow DOM, the `event.target` will + // point to the shadow root so we have to use `composedPath` instead. + return (event.composedPath ? event.composedPath()[0] : event.target) as T | null; } diff --git a/src/cdk/platform/platform.ts b/src/cdk/platform/platform.ts index 85d0cf9c48ce..8fe902f9ffb6 100755 --- a/src/cdk/platform/platform.ts +++ b/src/cdk/platform/platform.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Service, PLATFORM_ID} from '@angular/core'; +import {inject, Injectable, PLATFORM_ID} from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; // Whether the current platform supports the V8 Break Iterator. The V8 check @@ -28,7 +28,7 @@ try { * Service to detect the current platform by comparing the userAgent strings and * checking browser-specific global properties. */ -@Service() +@Injectable({providedIn: 'root'}) export class Platform { private _platformId = inject(PLATFORM_ID); @@ -85,4 +85,9 @@ export class Platform { // Safari browser should also use Webkit as its layout engine. /** Whether the current browser is Safari. */ SAFARI: boolean = this.isBrowser && /safari/i.test(navigator.userAgent) && this.WEBKIT; + + /** Backwards-compatible constructor. */ + constructor(..._args: unknown[]); + + constructor() {} } diff --git a/src/cdk/portal/dom-portal-outlet.ts b/src/cdk/portal/dom-portal-outlet.ts index 880f1eea5a35..811d14b6a4ea 100644 --- a/src/cdk/portal/dom-portal-outlet.ts +++ b/src/cdk/portal/dom-portal-outlet.ts @@ -59,8 +59,6 @@ export class DomPortalOutlet extends BasePortalOutlet { injector, ngModuleRef, projectableNodes: portal.projectableNodes || undefined, - bindings: portal.bindings || undefined, - directives: portal.directives || undefined, }); this.setDisposeFn(() => componentRef.destroy()); @@ -76,8 +74,6 @@ export class DomPortalOutlet extends BasePortalOutlet { elementInjector, environmentInjector, projectableNodes: portal.projectableNodes || undefined, - bindings: portal.bindings || undefined, - directives: portal.directives || undefined, }); appRef.attachView(componentRef.hostView); diff --git a/src/cdk/portal/portal-directives.ts b/src/cdk/portal/portal-directives.ts index 75a257e9e386..22269dd2111c 100644 --- a/src/cdk/portal/portal-directives.ts +++ b/src/cdk/portal/portal-directives.ts @@ -34,6 +34,8 @@ import {BasePortalOutlet, ComponentPortal, Portal, TemplatePortal, DomPortal} fr exportAs: 'cdkPortal', }) export class CdkPortal extends TemplatePortal { + constructor(...args: unknown[]); + constructor() { const templateRef = inject>(TemplateRef); const viewContainerRef = inject(ViewContainerRef); @@ -69,6 +71,12 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr /** Reference to the currently-attached component/view ref. */ private _attachedRef: CdkPortalOutletAttachedRef = null; + constructor(...args: unknown[]); + + constructor() { + super(); + } + /** Portal associated with the Portal outlet. */ @Input('cdkPortalOutlet') get portal(): Portal | null { @@ -132,8 +140,6 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr injector: portal.injector || viewContainerRef.injector, projectableNodes: portal.projectableNodes || undefined, ngModuleRef: this._moduleRef || undefined, - bindings: portal.bindings || undefined, - directives: portal.directives || undefined, }); // If we're using a view container that's different from the injected one (e.g. when the portal diff --git a/src/cdk/portal/portal.spec.ts b/src/cdk/portal/portal.spec.ts index e910307ba319..1d660f4291fd 100644 --- a/src/cdk/portal/portal.spec.ts +++ b/src/cdk/portal/portal.spec.ts @@ -7,7 +7,6 @@ import { ElementRef, EnvironmentInjector, Injector, - Input, QueryList, TemplateRef, ViewChild, @@ -16,8 +15,6 @@ import { createComponent, createEnvironmentInjector, inject, - inputBinding, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {DomPortalOutlet} from './dom-portal-outlet'; @@ -146,7 +143,7 @@ describe('Portals', () => { expect(testAppComponent.portalOutlet.hasAttached()).toBe(false); }); - it('should throw when trying to load an element without a parent into a DOM portal via a CdkPortalOutlet', () => { + it('should throw when trying to load an element without a parent into a DOM portal', () => { const testAppComponent = fixture.componentInstance; const element = document.createElement('div'); const domPortal = new DomPortal(element); @@ -158,7 +155,7 @@ describe('Portals', () => { }).toThrowError('DOM portal content must be attached to a parent node.'); }); - it('should not throw when restoring if the CdkPortalOutlet element was cleared', () => { + it('should not throw when restoring if the outlet element was cleared', () => { const testAppComponent = fixture.componentInstance; const parent = fixture.nativeElement.querySelector('.dom-portal-parent'); const domPortal = new DomPortal(testAppComponent.domPortalContent); @@ -332,7 +329,7 @@ describe('Portals', () => { expect(hostContainer.textContent).toContain('Mangosteen'); }); - it('should change the attached portal in a CdkPortalOutlet', () => { + it('should change the attached portal', () => { let testAppComponent = fixture.componentInstance; // Detect changes initially so that the component's ViewChildren are resolved. @@ -462,39 +459,6 @@ describe('Portals', () => { expect(fixture.nativeElement.textContent).toContain('Projectable node'); }); - - it('should be able to pass bindings to the component via a CdkPortalOutlet', () => { - let flavor = 'pepperoni'; - const componentPortal = new ComponentPortal(PizzaMsg, null, null, null, [ - inputBinding('flavor', () => flavor), - ]); - - fixture.componentInstance.selectedPortal = componentPortal; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - - const ref = fixture.componentInstance.portalOutlet.attachedRef as ComponentRef; - expect(ref.instance.flavor).toBe('pepperoni'); - - flavor = 'cheese'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - expect(ref.instance.flavor).toBe('cheese'); - }); - - it('should be able to pass directives to the component via a CdkPortalOutlet', () => { - const componentPortal = new ComponentPortal(PizzaMsg, null, null, null, undefined, [ - HighlightDirective, - ]); - - fixture.componentInstance.selectedPortal = componentPortal; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - - const ref = fixture.componentInstance.portalOutlet.attachedRef as ComponentRef; - const hostElement = ref.location.nativeElement as HTMLElement; - expect(hostElement.style.background).toBe('yellow'); - }); }); describe('DomPortalOutlet', () => { @@ -612,7 +576,7 @@ describe('Portals', () => { expect(someDomElement.innerHTML).toBe(''); }); - it('should change the attached portal in a DomPortalOutlet', () => { + it('should change the attached portal', () => { let fixture = TestBed.createComponent(ArbitraryViewContainerRefComponent); someViewContainerRef = fixture.componentInstance.viewContainerRef; @@ -665,7 +629,7 @@ describe('Portals', () => { 'Child environment', ); - @Component({template: '', changeDetection: ChangeDetectionStrategy.Eager}) + @Component({template: ''}) class ChildComponent {} const component = createComponent(ChildComponent, { @@ -706,7 +670,7 @@ describe('Portals', () => { expect(someDomElement.textContent!.trim()).toBe(''); }); - it('should throw when trying to load an element without a parent into a DOM portal via a DomPortalOutlet', () => { + it('should throw when trying to load an element without a parent into a DOM portal', () => { const fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); const element = document.createElement('div'); @@ -718,7 +682,7 @@ describe('Portals', () => { }).toThrowError('DOM portal content must be attached to a parent node.'); }); - it('should not throw when restoring if the DomPortalOutlet element was cleared', () => { + it('should not throw when restoring if the outlet element was cleared', () => { const fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); const portal = new DomPortal(fixture.componentInstance.domPortalContent); @@ -753,30 +717,6 @@ describe('Portals', () => { host.attachDomPortal(new DomPortal(fixture.componentInstance.domPortalContent)); expect(host.hasAttached()).toBe(true); }); - - it('should be able to pass bindings to the component via a DomPortalOutlet', () => { - const portal = new ComponentPortal(PizzaMsg, null, null, null, [ - inputBinding('flavor', () => 'pepperoni'), - ]); - - const componentInstance: PizzaMsg = portal.attach(host).instance; - someFixture.changeDetectorRef.markForCheck(); - someFixture.detectChanges(); - expect(componentInstance.flavor).toBe('pepperoni'); - }); - - it('should be able to pass directives to the component via a DomPortalOutlet', () => { - const portal = new ComponentPortal(PizzaMsg, null, null, null, undefined, [ - HighlightDirective, - ]); - - const ref = portal.attach(host); - someFixture.changeDetectorRef.markForCheck(); - someFixture.detectChanges(); - - const hostElement = ref.location.nativeElement as HTMLElement; - expect(hostElement.style.background).toBe('yellow'); - }); }); }); @@ -796,29 +736,14 @@ class ChocolateInjector { } } -/** Simple directive for testing directives support in ComponentPortal. */ -@Directive({ - selector: 'pizza-msg', -}) -class HighlightDirective { - private _elementRef = inject>(ElementRef); - - constructor() { - this._elementRef.nativeElement.style.background = 'yellow'; - } -} - /** Simple component for testing ComponentPortal. */ @Component({ selector: 'pizza-msg', template: '

    Pizza

    {{snack}}

    ', imports: [PortalModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg { snack = inject(Chocolate, {optional: true}); - - @Input() flavor = 'unknown'; } /** @@ -849,7 +774,6 @@ class SaveParentNodeOnInit implements AfterViewInit { `, imports: [SaveParentNodeOnInit], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArbitraryViewContainerRefComponent { viewContainerRef = inject(ViewContainerRef); @@ -891,7 +815,6 @@ class ArbitraryViewContainerRefComponent { `, imports: [CdkPortal, CdkPortalOutlet, PizzaMsg], - changeDetection: ChangeDetectionStrategy.Eager, }) class PortalTestApp { viewContainerRef = inject(ViewContainerRef); @@ -934,7 +857,6 @@ class PortalTestApp { `, imports: [CdkPortalOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) class UnboundPortalTestApp { @ViewChild(CdkPortalOutlet) portalOutlet!: CdkPortalOutlet; diff --git a/src/cdk/portal/portal.ts b/src/cdk/portal/portal.ts index 1db5d8532f66..e67cf3db866a 100644 --- a/src/cdk/portal/portal.ts +++ b/src/cdk/portal/portal.ts @@ -13,9 +13,6 @@ import { ComponentRef, EmbeddedViewRef, Injector, - Binding, - Type, - DirectiveWithBindings, } from '@angular/core'; import { throwNullPortalOutletError, @@ -102,31 +99,17 @@ export class ComponentPortal extends Portal> { */ projectableNodes?: Node[][] | null; - /** - * Bindings to apply to the created component. - */ - readonly bindings: Binding[] | null; - - /** - * Directives to apply to the created component. - */ - readonly directives: (Type | DirectiveWithBindings)[] | null; - constructor( component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, projectableNodes?: Node[][] | null, - bindings?: Binding[], - directives?: (Type | DirectiveWithBindings)[], ) { super(); this.component = component; this.viewContainerRef = viewContainerRef; this.injector = injector; this.projectableNodes = projectableNodes; - this.bindings = bindings || null; - this.directives = directives || null; } } diff --git a/src/cdk/private/style-loader.ts b/src/cdk/private/style-loader.ts index d67d3c639ff8..2377de1c1e42 100644 --- a/src/cdk/private/style-loader.ts +++ b/src/cdk/private/style-loader.ts @@ -12,7 +12,7 @@ import { createComponent, EnvironmentInjector, inject, - Service, + Injectable, Injector, Type, } from '@angular/core'; @@ -33,7 +33,7 @@ const appsWithLoaders = new WeakMap< * Service that loads structural styles dynamically * and ensures that they're only loaded once per app. */ -@Service() +@Injectable({providedIn: 'root'}) export class _CdkPrivateStyleLoader { private _appRef: ApplicationRef | undefined; private _injector = inject(Injector); diff --git a/src/cdk/private/trusted-types.ts b/src/cdk/private/trusted-types.ts index f04c9f577675..febe9fa717f6 100644 --- a/src/cdk/private/trusted-types.ts +++ b/src/cdk/private/trusted-types.ts @@ -45,17 +45,9 @@ function getPolicy(): TrustedTypePolicy | null { if (typeof window !== 'undefined') { const ttWindow = window as unknown as {trustedTypes?: TrustedTypePolicyFactory}; if (ttWindow.trustedTypes !== undefined) { - try { - policy = ttWindow.trustedTypes.createPolicy('angular#components', { - createHTML: (s: string) => s, - }); - } catch (error) { - // createPolicy can throw if the name is already registered, or if - // window.trustedTypes was DOM-clobbered with an HTML element before - // Angular bootstrapped. trustedHTMLFromString falls back to plain - // strings — sanitization in _setInnerHtml still runs. - console.error(error); - } + policy = ttWindow.trustedTypes.createPolicy('angular#components', { + createHTML: (s: string) => s, + }); } } } diff --git a/src/cdk/private/visually-hidden/visually-hidden.ts b/src/cdk/private/visually-hidden/visually-hidden.ts index 7f1a09a77fa8..6d2e51eaefc2 100644 --- a/src/cdk/private/visually-hidden/visually-hidden.ts +++ b/src/cdk/private/visually-hidden/visually-hidden.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; /** * Component used to load the .cdk-visually-hidden styles. @@ -17,5 +17,6 @@ import {Component, ViewEncapsulation} from '@angular/core'; exportAs: 'cdkVisuallyHidden', encapsulation: ViewEncapsulation.None, template: '', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class _VisuallyHiddenLoader {} diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel index 9f961f0813fd..9aa20e413dc6 100644 --- a/src/cdk/schematics/BUILD.bazel +++ b/src/cdk/schematics/BUILD.bazel @@ -1,5 +1,5 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config") -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project") package(default_visibility = [":__subpackages__"]) diff --git a/src/cdk/schematics/migration.json b/src/cdk/schematics/migration.json index a618045b50b4..7d641b126f1a 100644 --- a/src/cdk/schematics/migration.json +++ b/src/cdk/schematics/migration.json @@ -1,10 +1,10 @@ { "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { - "migration-v22": { - "version": "22.0.0-0", - "description": "Updates the Angular CDK to v22", - "factory": "./ng-update/index#updateToV22" + "migration-v21": { + "version": "21.0.0-0", + "description": "Updates the Angular CDK to v21", + "factory": "./ng-update/index#updateToV21" }, "ng-post-update": { "description": "Prints out results after ng-update.", diff --git a/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts index f2844e9f7ab1..2c962db32dff 100644 --- a/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts +++ b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts @@ -85,7 +85,7 @@ describe('CDK drag-drop schematic', () => { }); describe('style option', () => { - it('should respect the style option value', async () => { + it('should respect the option value', async () => { const tree = await runner.runSchematic( 'drag-drop', {style: 'scss', ...baseOptions}, @@ -140,7 +140,7 @@ describe('CDK drag-drop schematic', () => { .toContain('/projects/material/src/app/foo/foo.component.styl'); }); - it('should fall back to the @schematics/angular:component style option value', async () => { + it('should fall back to the @schematics/angular:component option value', async () => { const tree = await runner.runSchematic( 'drag-drop', baseOptions, @@ -151,13 +151,13 @@ describe('CDK drag-drop schematic', () => { }); describe('inlineStyle option', () => { - it('should respect the inlineStyle option value', async () => { + it('should respect the option value', async () => { const app = await createTestApp(runner); const tree = await runner.runSchematic('drag-drop', {inlineStyle: true, ...baseOptions}, app); expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css'); }); - it('should fall back to the @schematics/angular:component inlineStyle option value', async () => { + it('should fall back to the @schematics/angular:component option value', async () => { const tree = await runner.runSchematic( 'drag-drop', baseOptions, @@ -168,7 +168,7 @@ describe('CDK drag-drop schematic', () => { }); describe('inlineTemplate option', () => { - it('should respect the inlineTemplate option value', async () => { + it('should respect the option value', async () => { const app = await createTestApp(runner); const tree = await runner.runSchematic( 'drag-drop', @@ -178,7 +178,7 @@ describe('CDK drag-drop schematic', () => { expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html'); }); - it('should fall back to the @schematics/angular:component inlineTemplate option value', async () => { + it('should fall back to the @schematics/angular:component option value', async () => { const app = await createTestApp(runner, {inlineTemplate: true}); const tree = await runner.runSchematic('drag-drop', baseOptions, app); @@ -187,7 +187,7 @@ describe('CDK drag-drop schematic', () => { }); describe('skipTests option', () => { - it('should respect the skipTests option value', async () => { + it('should respect the option value', async () => { const tree = await runner.runSchematic( 'drag-drop', {skipTests: true, ...baseOptions}, @@ -222,7 +222,7 @@ describe('CDK drag-drop schematic', () => { expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts'); }); - it('should fall back to the @schematics/angular:component skipTests option value', async () => { + it('should fall back to the @schematics/angular:component option value', async () => { const tree = await runner.runSchematic( 'drag-drop', baseOptions, diff --git a/src/cdk/schematics/ng-update/index.ts b/src/cdk/schematics/ng-update/index.ts index 9e756a189058..68f963d2c521 100644 --- a/src/cdk/schematics/ng-update/index.ts +++ b/src/cdk/schematics/ng-update/index.ts @@ -13,10 +13,10 @@ import {createMigrationSchematicRule, NullableDevkitMigration} from './devkit-mi const cdkMigrations: NullableDevkitMigration[] = []; -/** Entry point for the migration schematics with target of Angular CDK 22.0.0 */ -export function updateToV22(): Rule { +/** Entry point for the migration schematics with target of Angular CDK 20.0.0 */ +export function updateToV21(): Rule { return createMigrationSchematicRule( - TargetVersion.V22, + TargetVersion.V21, cdkMigrations, cdkUpgradeData, onMigrationComplete, diff --git a/src/cdk/schematics/testing/test-case-setup.ts b/src/cdk/schematics/testing/test-case-setup.ts index 613509e5caef..6ed317de850f 100644 --- a/src/cdk/schematics/testing/test-case-setup.ts +++ b/src/cdk/schematics/testing/test-case-setup.ts @@ -121,7 +121,7 @@ export async function createTestCaseSetup( export function findBazelVersionTestCases(basePath: string) { const testCasesMap = new Map(); const manifestPath = process.env['RUNFILES_MANIFEST_FILE']!; - const runfilesDir = process.env['JS_BINARY__RUNFILES']; + const runfilesDir = process.env['RUNFILES']; // In case we are not on Windows where runfiles are symlinked, we just find all // test case files by using "glob" and store them in our result map. diff --git a/src/cdk/schematics/tsconfig.json b/src/cdk/schematics/tsconfig.json index e0e5ea33edc9..afc015d66989 100644 --- a/src/cdk/schematics/tsconfig.json +++ b/src/cdk/schematics/tsconfig.json @@ -4,6 +4,7 @@ "lib": ["es2017"], "module": "commonjs", "moduleResolution": "node", + "rootDir": ".", "strictNullChecks": true, "strictPropertyInitialization": true, "noPropertyAccessFromIndexSignature": true, diff --git a/src/cdk/schematics/update-tool/target-version.ts b/src/cdk/schematics/update-tool/target-version.ts index 6ed811c95c7e..ba2646bb0a62 100644 --- a/src/cdk/schematics/update-tool/target-version.ts +++ b/src/cdk/schematics/update-tool/target-version.ts @@ -10,7 +10,7 @@ // tslint:disable-next-line:prefer-const-enum export enum TargetVersion { - V22 = 'version 22', + V21 = 'version 21', } /** diff --git a/src/cdk/scrolling/scroll-dispatcher.spec.ts b/src/cdk/scrolling/scroll-dispatcher.spec.ts index 286ffb262af0..eca90f19a365 100644 --- a/src/cdk/scrolling/scroll-dispatcher.spec.ts +++ b/src/cdk/scrolling/scroll-dispatcher.spec.ts @@ -1,5 +1,5 @@ -import {TestBed, ComponentFixture} from '@angular/core/testing'; -import {Component, ViewChild, ElementRef, ChangeDetectionStrategy} from '@angular/core'; +import {TestBed, fakeAsync, ComponentFixture, tick} from '@angular/core/testing'; +import {Component, ViewChild, ElementRef} from '@angular/core'; import {CdkScrollable, ScrollDispatcher, ScrollingModule} from './public-api'; import {dispatchFakeEvent} from '../testing/private'; @@ -31,7 +31,7 @@ describe('ScrollDispatcher', () => { expect(scroll.scrollContainers.has(componentScrollable)).toBe(false); }); - it('should notify through the directive and service that a scroll event occurred', async () => { + it('should notify through the directive and service that a scroll event occurred', fakeAsync(() => { // Listen for notifications from scroll directive const scrollable = fixture.componentInstance.scrollable; const directiveSpy = jasmine.createSpy('directive scroll callback'); @@ -55,9 +55,9 @@ describe('ScrollDispatcher', () => { expect(serviceSpy).not.toHaveBeenCalled(); // After the throttle time, the notification should be sent. - await new Promise(resolve => setTimeout(resolve, throttleTime + 10)); + tick(throttleTime); expect(serviceSpy).toHaveBeenCalled(); - }); + })); it('should be able to unsubscribe from the global scrollable', () => { const spy = jasmine.createSpy('global scroll callback'); @@ -259,7 +259,6 @@ describe('ScrollDispatcher', () => { @Component({ template: `
    `, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ScrollingComponent { @ViewChild(CdkScrollable) scrollable!: CdkScrollable; @@ -278,7 +277,6 @@ class ScrollingComponent {
    `, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedScrollingComponent { @ViewChild('interestingElement') interestingElement!: ElementRef; diff --git a/src/cdk/scrolling/scroll-dispatcher.ts b/src/cdk/scrolling/scroll-dispatcher.ts index af87faff7be7..912cb3f79e52 100644 --- a/src/cdk/scrolling/scroll-dispatcher.ts +++ b/src/cdk/scrolling/scroll-dispatcher.ts @@ -8,69 +8,64 @@ import {coerceElement} from '../coercion'; import {Platform} from '../platform'; -import {ElementRef, Service, NgZone, OnDestroy, RendererFactory2, inject} from '@angular/core'; +import {ElementRef, Injectable, NgZone, OnDestroy, RendererFactory2, inject} from '@angular/core'; import {of as observableOf, Subject, Subscription, Observable, Observer} from 'rxjs'; import {auditTime, filter} from 'rxjs/operators'; +import type {CdkScrollable} from './scrollable'; /** Time in ms to throttle the scrolling events by default. */ export const DEFAULT_SCROLL_TIME = 20; -/** Scrollable instance that can be registered with the `ScrollDispatcher`. */ -export interface ScrollDispatcherTarget { - /** Observable that emits when the element is scrolled. */ - elementScrolled(): Observable; - - /** Gets the `ElementRef` representing the scrollable element. */ - getElementRef(): ElementRef; -} - /** - * Service contained all registered scroll targets and emits - * an event when any one of them emits a scrolled event. + * Service contained all registered Scrollable references and emits an event when any one of the + * Scrollable references emit a scrolled event. */ -@Service() +@Injectable({providedIn: 'root'}) export class ScrollDispatcher implements OnDestroy { private _ngZone = inject(NgZone); private _platform = inject(Platform); private _renderer = inject(RendererFactory2).createRenderer(null, null); private _cleanupGlobalListener: (() => void) | undefined; - /** Subject for notifying that a registered element has been scrolled. */ - private readonly _scrolled = new Subject(); + constructor(...args: unknown[]); + constructor() {} + + /** Subject for notifying that a registered scrollable reference element has been scrolled. */ + private readonly _scrolled = new Subject(); /** Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards. */ private _scrolledCount = 0; /** - * Map of all the scrollable targets that are registered with the service and their + * Map of all the scrollable references that are registered with the service and their * scroll event subscriptions. */ - readonly scrollContainers: Map = new Map(); + scrollContainers: Map = new Map(); /** * Registers a scrollable instance with the service and listens for its scrolled events. When the * scrollable is scrolled, the service emits the event to its scrolled observable. - * @param target Scrollable instance to be registered. + * @param scrollable Scrollable instance to be registered. */ - register(target: ScrollDispatcherTarget): void { - if (!this.scrollContainers.has(target)) { + register(scrollable: CdkScrollable): void { + if (!this.scrollContainers.has(scrollable)) { this.scrollContainers.set( - target, - target.elementScrolled().subscribe(() => this._scrolled.next(target)), + scrollable, + scrollable.elementScrolled().subscribe(() => this._scrolled.next(scrollable)), ); } } /** * De-registers a Scrollable reference and unsubscribes from its scroll event observable. - * @param target Scrollable instance to be deregistered. + * @param scrollable Scrollable instance to be deregistered. */ - deregister(target: ScrollDispatcherTarget): void { - const ref = this.scrollContainers.get(target); + deregister(scrollable: CdkScrollable): void { + const scrollableReference = this.scrollContainers.get(scrollable); - if (ref) { - ref.unsubscribe(); - this.scrollContainers.delete(target); + if (scrollableReference) { + scrollableReference.unsubscribe(); + this.scrollContainers.delete(scrollable); } } @@ -84,12 +79,12 @@ export class ScrollDispatcher implements OnDestroy { * If you need to update any data bindings as a result of a scroll event, you have * to run the callback using `NgZone.run`. */ - scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME): Observable { + scrolled(auditTimeInMs: number = DEFAULT_SCROLL_TIME): Observable { if (!this._platform.isBrowser) { return observableOf(); } - return new Observable((observer: Observer) => { + return new Observable((observer: Observer) => { if (!this._cleanupGlobalListener) { this._cleanupGlobalListener = this._ngZone.runOutsideAngular(() => this._renderer.listen('document', 'scroll', () => this._scrolled.next()), @@ -133,7 +128,7 @@ export class ScrollDispatcher implements OnDestroy { ancestorScrolled( elementOrElementRef: ElementRef | HTMLElement, auditTimeInMs?: number, - ): Observable { + ): Observable { const ancestors = this.getAncestorScrollContainers(elementOrElementRef); return this.scrolled(auditTimeInMs).pipe( @@ -141,15 +136,13 @@ export class ScrollDispatcher implements OnDestroy { ); } - /** Returns all registered containers that contain the provided element. */ - getAncestorScrollContainers( - elementOrElementRef: ElementRef | HTMLElement, - ): ScrollDispatcherTarget[] { - const scrollingContainers: ScrollDispatcherTarget[] = []; + /** Returns all registered Scrollables that contain the provided element. */ + getAncestorScrollContainers(elementOrElementRef: ElementRef | HTMLElement): CdkScrollable[] { + const scrollingContainers: CdkScrollable[] = []; - this.scrollContainers.forEach((_, target: ScrollDispatcherTarget) => { - if (this._targetContainsElement(target, elementOrElementRef)) { - scrollingContainers.push(target); + this.scrollContainers.forEach((_subscription: Subscription, scrollable: CdkScrollable) => { + if (this._scrollableContainsElement(scrollable, elementOrElementRef)) { + scrollingContainers.push(scrollable); } }); @@ -157,17 +150,17 @@ export class ScrollDispatcher implements OnDestroy { } /** Returns true if the element is contained within the provided Scrollable. */ - private _targetContainsElement( - scrollable: ScrollDispatcherTarget, + private _scrollableContainsElement( + scrollable: CdkScrollable, elementOrElementRef: ElementRef | HTMLElement, ): boolean { let element: HTMLElement | null = coerceElement(elementOrElementRef); - let targetElement = scrollable.getElementRef().nativeElement; + let scrollableElement = scrollable.getElementRef().nativeElement; // Traverse through the element parents until we reach null, checking if any of the elements // are the scrollable's element. do { - if (element == targetElement) { + if (element == scrollableElement) { return true; } } while ((element = element!.parentElement)); diff --git a/src/cdk/scrolling/scroll-dispatcher.zone.spec.ts b/src/cdk/scrolling/scroll-dispatcher.zone.spec.ts index 7f4f1aec8ca6..676c5155990d 100644 --- a/src/cdk/scrolling/scroll-dispatcher.zone.spec.ts +++ b/src/cdk/scrolling/scroll-dispatcher.zone.spec.ts @@ -1,10 +1,4 @@ -import { - Component, - ElementRef, - ViewChild, - provideZoneChangeDetection, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ElementRef, ViewChild, provideZoneChangeDetection} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {dispatchFakeEvent} from '../testing/private'; import {ScrollDispatcher} from './scroll-dispatcher'; @@ -42,7 +36,6 @@ describe('ScrollDispatcher Zone.js integration', () => { @Component({ template: `
    `, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ScrollingComponent { @ViewChild(CdkScrollable) scrollable!: CdkScrollable; diff --git a/src/cdk/scrolling/scrollable.spec.ts b/src/cdk/scrolling/scrollable.spec.ts index d4baec42bb52..950d3a347e52 100644 --- a/src/cdk/scrolling/scrollable.spec.ts +++ b/src/cdk/scrolling/scrollable.spec.ts @@ -1,6 +1,6 @@ import {Direction} from '../bidi'; import {CdkScrollable, ScrollingModule} from '../scrolling'; -import {Component, ElementRef, Input, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, Input, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; function expectOverlapping(el1: ElementRef, el2: ElementRef, expected = true) { @@ -55,7 +55,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(maxOffset); }); - it('should scrollTo top-left in LTR', () => { + it('should scrollTo top-left', () => { testComponent.scrollable.scrollTo({top: 0, left: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, true); @@ -71,7 +71,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(maxOffset); }); - it('should scrollTo bottom-right in LTR', () => { + it('should scrollTo bottom-right', () => { testComponent.scrollable.scrollTo({bottom: 0, right: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -87,7 +87,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(0); }); - it('should scroll to top-end in LTR', () => { + it('should scroll to top-end', () => { testComponent.scrollable.scrollTo({top: 0, end: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -103,7 +103,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(0); }); - it('should scroll to bottom-start in LTR', () => { + it('should scroll to bottom-start', () => { testComponent.scrollable.scrollTo({bottom: 0, start: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -144,7 +144,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(maxOffset); }); - it('should scrollTo top-left in RTL', () => { + it('should scrollTo top-left', () => { testComponent.scrollable.scrollTo({top: 0, left: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -160,7 +160,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(0); }); - it('should scrollTo bottom-right in RTL', () => { + it('should scrollTo bottom-right', () => { testComponent.scrollable.scrollTo({bottom: 0, right: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -176,7 +176,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(maxOffset); }); - it('should scroll to top-end in RTL', () => { + it('should scroll to top-end', () => { testComponent.scrollable.scrollTo({top: 0, end: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -192,7 +192,7 @@ describe('CdkScrollable', () => { expect(testComponent.scrollable.measureScrollOffset('end')).toBe(0); }); - it('should scroll to bottom-start in RTL', () => { + it('should scroll to bottom-start', () => { testComponent.scrollable.scrollTo({bottom: 0, start: 0}); expectOverlapping(testComponent.scrollContainer, testComponent.firstRowStart, false); @@ -241,7 +241,6 @@ describe('CdkScrollable', () => { } `, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ScrollableViewport { @Input() dir: Direction = 'ltr'; diff --git a/src/cdk/scrolling/scrollable.ts b/src/cdk/scrolling/scrollable.ts index b18844eba9ea..1469e210e3db 100644 --- a/src/cdk/scrolling/scrollable.ts +++ b/src/cdk/scrolling/scrollable.ts @@ -10,7 +10,7 @@ import {Directionality} from '../bidi'; import {getRtlScrollAxisType, RtlScrollAxisType, supportsScrollBehavior} from '../platform'; import {Directive, ElementRef, NgZone, OnDestroy, OnInit, Renderer2, inject} from '@angular/core'; import {Observable, Subject} from 'rxjs'; -import {ScrollDispatcher, ScrollDispatcherTarget} from './scroll-dispatcher'; +import {ScrollDispatcher} from './scroll-dispatcher'; export type _Without = {[P in keyof T]?: never}; export type _XOR = (_Without & U) | (_Without & T); @@ -39,7 +39,7 @@ export type ExtendedScrollToOptions = _XAxis & _YAxis & ScrollOptions; @Directive({ selector: '[cdk-scrollable], [cdkScrollable]', }) -export class CdkScrollable implements ScrollDispatcherTarget, OnInit, OnDestroy { +export class CdkScrollable implements OnInit, OnDestroy { protected elementRef = inject>(ElementRef); protected scrollDispatcher = inject(ScrollDispatcher); protected ngZone = inject(NgZone); @@ -50,6 +50,9 @@ export class CdkScrollable implements ScrollDispatcherTarget, OnInit, OnDestroy private _cleanupScroll: (() => void) | undefined; private _elementScrolled = new Subject(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { this._cleanupScroll = this.ngZone.runOutsideAngular(() => this._renderer.listen(this._scrollElement, 'scroll', event => diff --git a/src/cdk/scrolling/viewport-ruler.spec.ts b/src/cdk/scrolling/viewport-ruler.spec.ts index 110db7da7549..bf068b6d23c3 100644 --- a/src/cdk/scrolling/viewport-ruler.spec.ts +++ b/src/cdk/scrolling/viewport-ruler.spec.ts @@ -1,4 +1,4 @@ -import {TestBed} from '@angular/core/testing'; +import {TestBed, fakeAsync, tick} from '@angular/core/testing'; import {dispatchFakeEvent} from '../testing/private'; import {ViewportRuler} from './viewport-ruler'; @@ -109,17 +109,17 @@ describe('ViewportRuler', () => { subscription.unsubscribe(); }); - it('should be able to throttle the callback', async () => { + it('should be able to throttle the callback', fakeAsync(() => { const spy = jasmine.createSpy('viewport changed spy'); - const subscription = viewportRuler.change(10).subscribe(spy); + const subscription = viewportRuler.change(1337).subscribe(spy); dispatchFakeEvent(window, 'resize'); expect(spy).not.toHaveBeenCalled(); - await new Promise(resolve => setTimeout(resolve, 20)); + tick(1337); expect(spy).toHaveBeenCalledTimes(1); subscription.unsubscribe(); - }); + })); }); }); diff --git a/src/cdk/scrolling/viewport-ruler.ts b/src/cdk/scrolling/viewport-ruler.ts index e2c8a203e6f1..b4de195f7495 100644 --- a/src/cdk/scrolling/viewport-ruler.ts +++ b/src/cdk/scrolling/viewport-ruler.ts @@ -7,7 +7,7 @@ */ import {Platform} from '../platform'; -import {Service, NgZone, OnDestroy, RendererFactory2, inject, DOCUMENT} from '@angular/core'; +import {Injectable, NgZone, OnDestroy, RendererFactory2, inject, DOCUMENT} from '@angular/core'; import {Observable, Subject} from 'rxjs'; import {auditTime} from 'rxjs/operators'; @@ -24,7 +24,7 @@ export interface ViewportScrollPosition { * Simple utility for getting the bounds of the browser viewport. * @docs-private */ -@Service() +@Injectable({providedIn: 'root'}) export class ViewportRuler implements OnDestroy { private _platform = inject(Platform); private _listeners: (() => void)[] | undefined; @@ -38,6 +38,8 @@ export class ViewportRuler implements OnDestroy { /** Used to reference correct document/window */ protected _document = inject(DOCUMENT); + constructor(...args: unknown[]); + constructor() { const ngZone = inject(NgZone); const renderer = inject(RendererFactory2).createRenderer(null, null); diff --git a/src/cdk/scrolling/viewport-ruler.zone.spec.ts b/src/cdk/scrolling/viewport-ruler.zone.spec.ts index af3b70a8790d..dccbd069ef32 100644 --- a/src/cdk/scrolling/viewport-ruler.zone.spec.ts +++ b/src/cdk/scrolling/viewport-ruler.zone.spec.ts @@ -4,7 +4,7 @@ import {Subscription} from 'rxjs'; import {dispatchFakeEvent} from '../testing/private'; import {ViewportRuler} from './viewport-ruler'; -describe('ViewportRuler zone behavior', () => { +describe('ViewportRuler', () => { let viewportRuler: ViewportRuler; let ngZone: NgZone; diff --git a/src/cdk/scrolling/virtual-for-of.ts b/src/cdk/scrolling/virtual-for-of.ts index 8469c68e94b7..2173510ea50b 100644 --- a/src/cdk/scrolling/virtual-for-of.ts +++ b/src/cdk/scrolling/virtual-for-of.ts @@ -182,6 +182,8 @@ export class CdkVirtualForOf private readonly _destroyed = new Subject(); + constructor(...args: unknown[]); + constructor() { const ngZone = inject(NgZone); diff --git a/src/cdk/scrolling/virtual-scroll-viewport.scss b/src/cdk/scrolling/virtual-scroll-viewport.scss index 599b76fcb830..47a102b6a2ed 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.scss +++ b/src/cdk/scrolling/virtual-scroll-viewport.scss @@ -47,10 +47,6 @@ cdk-virtual-scroll-viewport { overflow: auto; will-change: scroll-position; contain: strict; - - // Prevent browser behaviors from interfering with virtual scrolling. - overflow-anchor: none; - scroll-behavior: auto; } // Wrapper element for the rendered content. This element will be transformed to push the rendered @@ -63,6 +59,7 @@ cdk-virtual-scroll-viewport { // Note: We can't put `will-change: transform;` here because it causes Safari to not update the // viewport's `scrollHeight` when the spacer's transform changes. + [dir='rtl'] & { right: 0; left: auto; @@ -89,6 +86,7 @@ cdk-virtual-scroll-viewport { // Note: We can't put `will-change: transform;` here because it causes Safari to not update the // viewport's `scrollHeight` when the spacer's transform changes. + [dir='rtl'] & { transform-origin: 100% 0; } diff --git a/src/cdk/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts index 4a22f31b652d..bfbb970cbd21 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk/scrolling/virtual-scroll-viewport.spec.ts @@ -14,17 +14,19 @@ import { ViewContainerRef, ViewEncapsulation, inject, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {Subject} from 'rxjs'; import {dispatchFakeEvent} from '../testing/private'; describe('CdkVirtualScrollViewport', () => { - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - describe('with FixedSizeVirtualScrollStrategy', () => { let fixture: ComponentFixture; let testComponent: FixedSizeVirtualScroll; @@ -36,8 +38,8 @@ describe('CdkVirtualScrollViewport', () => { viewport = testComponent.viewport; }); - it('should render initial state', async () => { - await finishInit(fixture); + it('should render initial state', fakeAsync(() => { + finishInit(fixture); const contentWrapper = viewport.elementRef.nativeElement.querySelector( '.cdk-virtual-scroll-content-wrapper', @@ -45,144 +47,144 @@ describe('CdkVirtualScrollViewport', () => { expect(contentWrapper.children.length) .withContext('should render 4 50px items to fill 200px space') .toBe(4); - }); + })); - it('should get the data length', async () => { - await finishInit(fixture); + it('should get the data length', fakeAsync(() => { + finishInit(fixture); expect(viewport.getDataLength()).toBe(testComponent.items.length); - }); + })); - it('should get the viewport size', async () => { - await finishInit(fixture); + it('should get the viewport size', fakeAsync(() => { + finishInit(fixture); expect(viewport.getViewportSize()).toBe(testComponent.viewportSize); - }); + })); - it('should update viewport size', async () => { + it('should update viewport size', fakeAsync(() => { testComponent.viewportSize = 300; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); viewport.checkViewportSize(); expect(viewport.getViewportSize()).toBe(300); testComponent.viewportSize = 500; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); viewport.checkViewportSize(); expect(viewport.getViewportSize()).toBe(500); - await fixture.whenStable(); - }); + flush(); + })); - it('should update the viewport size when the page viewport changes', async () => { - await finishInit(fixture); + it('should update the viewport size when the page viewport changes', fakeAsync(() => { + finishInit(fixture); spyOn(viewport, 'checkViewportSize').and.callThrough(); dispatchFakeEvent(window, 'resize'); fixture.detectChanges(); - await new Promise(resolve => setTimeout(resolve, 20)); // The resize listener is debounced so we need to flush it. + tick(20); // The resize listener is debounced so we need to flush it. expect(viewport.checkViewportSize).toHaveBeenCalled(); - }); + })); - it('should get the rendered range', async () => { - await finishInit(fixture); + it('should get the rendered range', fakeAsync(() => { + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext('should render the first 4 50px items to fill 200px space') .toEqual({start: 0, end: 4}); - }); + })); - it('should contract the rendered range when changing to less data', async () => { - await finishInit(fixture); + it('should contract the rendered range when changing to less data', fakeAsync(() => { + finishInit(fixture); expect(viewport.getRenderedRange()).toEqual({start: 0, end: 4}); fixture.componentInstance.items = [0, 1]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()).toEqual({start: 0, end: 2}); fixture.componentInstance.items = []; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()).toEqual({start: 0, end: 0}); - }); + })); - it('should get the rendered content offset', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize + 5); + it('should get the rendered content offset', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize + 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should have 50px offset since first 50px item is not rendered') .toBe(testComponent.itemSize); - }); + })); - it('should get the scroll offset', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize + 5); + it('should get the scroll offset', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize + 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize + 5); - }); + })); - it('should get the rendered content size', async () => { - await finishInit(fixture); + it('should get the rendered content size', fakeAsync(() => { + finishInit(fixture); expect(viewport.measureRenderedContentSize()) .withContext('should render 4 50px items with combined size of 200px to fill 200px space') .toBe(testComponent.viewportSize); - }); + })); - it('should measure range size', async () => { - await finishInit(fixture); + it('should measure range size', fakeAsync(() => { + finishInit(fixture); expect(viewport.measureRangeSize({start: 1, end: 3})) .withContext('combined size of 2 50px items should be 100px') .toBe(testComponent.itemSize * 2); - }); + })); - it('should measure range size when items has a margin', async () => { + it('should measure range size when items has a margin', fakeAsync(() => { fixture.componentInstance.hasMargin = true; - await finishInit(fixture); + finishInit(fixture); expect(viewport.measureRangeSize({start: 1, end: 3})) .withContext('combined size of 2 50px items with a 10px margin should be 110px') .toBe(testComponent.itemSize * 2 + 10); - }); + })); - it('should set total content size', async () => { - await finishInit(fixture); + it('should set total content size', fakeAsync(() => { + finishInit(fixture); viewport.setTotalContentSize(10000); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(viewport.elementRef.nativeElement.scrollHeight).toBe(10000); - }); + })); - it('should set total content size in horizontal mode', async () => { + it('should set total content size in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.setTotalContentSize(10000); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(viewport.elementRef.nativeElement.scrollWidth).toBe(10000); - }); + })); - it('should set a class based on the orientation', async () => { - await finishInit(fixture); + it('should set a class based on the orientation', fakeAsync(() => { + finishInit(fixture); const viewportElement: HTMLElement = fixture.nativeElement.querySelector( '.cdk-virtual-scroll-viewport', ); @@ -194,22 +196,22 @@ describe('CdkVirtualScrollViewport', () => { fixture.detectChanges(); expect(viewportElement.classList).toContain('cdk-virtual-scroll-orientation-horizontal'); - }); + })); - it('should set the vertical class if an invalid orientation is set', async () => { + it('should set the vertical class if an invalid orientation is set', fakeAsync(() => { testComponent.orientation = 'diagonal' as any; - await finishInit(fixture); + finishInit(fixture); const viewportElement: HTMLElement = fixture.nativeElement.querySelector( '.cdk-virtual-scroll-viewport', ); expect(viewportElement.classList).toContain('cdk-virtual-scroll-orientation-vertical'); - }); + })); - it('should set rendered range', async () => { - await finishInit(fixture); + it('should set rendered range', fakeAsync(() => { + finishInit(fixture); viewport.setRenderedRange({start: 2, end: 3}); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const items = fixture.elementRef.nativeElement.querySelectorAll('.item'); @@ -217,105 +219,103 @@ describe('CdkVirtualScrollViewport', () => { expect(items[0].innerText.trim()) .withContext('Expected item with index 2 to be rendered') .toBe('2 - 2'); - }); + })); - it('should set content offset to top of content', async () => { - await finishInit(fixture); + it('should set content offset to top of content', fakeAsync(() => { + finishInit(fixture); viewport.setRenderedContentOffset(10, 'to-start'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()).toBe(10); - }); + })); - it('should set content offset to bottom of content', async () => { - await finishInit(fixture); + it('should set content offset to bottom of content', fakeAsync(() => { + finishInit(fixture); const contentSize = viewport.measureRenderedContentSize(); expect(contentSize).toBeGreaterThan(0); viewport.setRenderedContentOffset(contentSize + 10, 'to-end'); - await fixture.whenStable(); - await new Promise(resolve => requestAnimationFrame(resolve)); // wait for afterNextRender + flush(); expect(viewport.getOffsetToRenderedContentStart()).toBe(10); - }); + })); - it('should scroll to offset', async () => { - await finishInit(fixture); + it('should scroll to offset', fakeAsync(() => { + finishInit(fixture); viewport.scrollToOffset(testComponent.itemSize * 2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); - await wait(100); + flush(); expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize * 2); expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - }); + })); - it('should scroll to index', async () => { - await finishInit(fixture); + it('should scroll to index', fakeAsync(() => { + finishInit(fixture); viewport.scrollToIndex(2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize * 2); expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - }); + })); - it('should scroll to offset in horizontal mode', async () => { + it('should scroll to offset in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.scrollToOffset(testComponent.itemSize * 2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize * 2); expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - }); + })); - it('should scroll to index in horizontal mode', async () => { + it('should scroll to index in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.scrollToIndex(2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset()).toBe(testComponent.itemSize * 2); expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); - }); + })); - it('should output scrolled index', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 2 - 1); + it('should output scrolled index', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2 - 1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.scrolledToIndex).toBe(1); - await triggerScroll(viewport, testComponent.itemSize * 2); + triggerScroll(viewport, testComponent.itemSize * 2); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.scrolledToIndex).toBe(2); - }); + })); - it('should update viewport as user scrolls down', async () => { - await finishInit(fixture); + it('should update viewport as user scrolls down', fakeAsync(() => { + finishInit(fixture); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 1; offset <= maxOffset; offset += 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const expectedRange = { start: Math.floor(offset / testComponent.itemSize), @@ -333,17 +333,17 @@ describe('CdkVirtualScrollViewport', () => { .withContext(`rendered content size should match expected value at offset ${offset}`) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize); } - }); + })); - it('should update viewport as user scrolls up', async () => { - await finishInit(fixture); + it('should update viewport as user scrolls up', fakeAsync(() => { + finishInit(fixture); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = maxOffset - 1; offset >= 0; offset -= 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const expectedRange = { start: Math.floor(offset / testComponent.itemSize), @@ -361,12 +361,12 @@ describe('CdkVirtualScrollViewport', () => { .withContext(`rendered content size should match expected value at offset ${offset}`) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize); } - }); + })); - it('should render buffer element at the end when scrolled to the top', async () => { + it('should render buffer element at the end when scrolled to the top', fakeAsync(() => { testComponent.minBufferPx = testComponent.itemSize; testComponent.maxBufferPx = testComponent.itemSize; - await finishInit(fixture); + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext( @@ -374,16 +374,15 @@ describe('CdkVirtualScrollViewport', () => { 'plus one buffer element at the end', ) .toEqual({start: 0, end: 5}); - }); + })); - it('should render buffer element at the start and end when scrolled to the middle', async () => { + it('should render buffer element at the start and end when scrolled to the middle', fakeAsync(() => { testComponent.minBufferPx = testComponent.itemSize; testComponent.maxBufferPx = testComponent.itemSize; - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 2); + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); fixture.detectChanges(); - await fixture.whenStable(); - await wait(100); + flush(); expect(viewport.getRenderedRange()) .withContext( @@ -391,15 +390,15 @@ describe('CdkVirtualScrollViewport', () => { 'buffer element at the start and end', ) .toEqual({start: 1, end: 7}); - }); + })); - it('should render buffer element at the start when scrolled to the bottom', async () => { + it('should render buffer element at the start when scrolled to the bottom', fakeAsync(() => { testComponent.minBufferPx = testComponent.itemSize; testComponent.maxBufferPx = testComponent.itemSize; - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 6); + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 6); fixture.detectChanges(); - await wait(100); + flush(); expect(viewport.getRenderedRange()) .withContext( @@ -407,13 +406,13 @@ describe('CdkVirtualScrollViewport', () => { 'buffer element at the start', ) .toEqual({start: 5, end: 10}); - }); + })); - it('should handle dynamic item size', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 2); + it('should handle dynamic item size', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); fixture.detectChanges(); - await wait(100); + flush(); expect(viewport.getRenderedRange()) .withContext('should render 4 50px items to fill 200px space') @@ -422,18 +421,18 @@ describe('CdkVirtualScrollViewport', () => { testComponent.itemSize *= 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(100); + flush(); expect(viewport.getRenderedRange()) .withContext('should render 2 100px items to fill 200px space') .toEqual({start: 1, end: 3}); - }); + })); - it('should handle dynamic buffer size', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 2); + it('should handle dynamic buffer size', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('should render 4 50px items to fill 200px space') @@ -443,18 +442,18 @@ describe('CdkVirtualScrollViewport', () => { testComponent.maxBufferPx = testComponent.itemSize; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('should expand to 1 buffer element on each side') .toEqual({start: 1, end: 7}); - }); + })); - it('should handle dynamic item array', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 6); + it('should handle dynamic item array', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 6); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should be scrolled to bottom of 10 item list') @@ -463,18 +462,18 @@ describe('CdkVirtualScrollViewport', () => { testComponent.items = Array(5).fill(0); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should be scrolled to bottom of 5 item list') .toBe(testComponent.itemSize); - }); + })); - it('should handle dynamic item array with dynamic buffer', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 6); + it('should handle dynamic item array with dynamic buffer', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 6); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should be scrolled to bottom of 10 item list') @@ -486,19 +485,19 @@ describe('CdkVirtualScrollViewport', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should render from first item') .toBe(0); - }); + })); - it('should handle dynamic item array keeping position when possible', async () => { + it('should handle dynamic item array keeping position when possible', fakeAsync(() => { testComponent.items = Array(100).fill(0); - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * 50); + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 50); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should be scrolled to index 50 item list') @@ -507,23 +506,23 @@ describe('CdkVirtualScrollViewport', () => { testComponent.items = Array(54).fill(0); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should be kept the scroll position') .toBe(testComponent.itemSize * 50); - }); + })); - it('should update viewport as user scrolls right in horizontal mode', async () => { + it('should update viewport as user scrolls right in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 1; offset <= maxOffset; offset += 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const expectedRange = { start: Math.floor(offset / testComponent.itemSize), @@ -541,18 +540,18 @@ describe('CdkVirtualScrollViewport', () => { .withContext(`rendered content size should match expected value at offset ${offset}`) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize); } - }); + })); - it('should update viewport as user scrolls left in horizontal mode', async () => { + it('should update viewport as user scrolls left in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = maxOffset - 1; offset >= 0; offset -= 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const expectedRange = { start: Math.floor(offset / testComponent.itemSize), @@ -570,22 +569,22 @@ describe('CdkVirtualScrollViewport', () => { .withContext(`rendered content size should match expected value at offset ${offset}`) .toBe((expectedRange.end - expectedRange.start) * testComponent.itemSize); } - }); + })); - it('should work with a Set', async () => { + it('should work with a Set', fakeAsync(() => { const data = new Set(['hello', 'world', 'how', 'are', 'you']); testComponent.items = data as any; - await finishInit(fixture); + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext('newly emitted items should be rendered') .toEqual({start: 0, end: 4}); - }); + })); - it('should work with an Observable', async () => { + it('should work with an Observable', fakeAsync(() => { const data = new Subject(); testComponent.items = data as any; - await finishInit(fixture); + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext('no items should be rendered') @@ -593,17 +592,17 @@ describe('CdkVirtualScrollViewport', () => { data.next([1, 2, 3]); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('newly emitted items should be rendered') .toEqual({start: 0, end: 3}); - }); + })); - it('should work with a DataSource', async () => { + it('should work with a DataSource', fakeAsync(() => { const data = new Subject(); testComponent.items = new ArrayDataSource(data) as any; - await finishInit(fixture); + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext('no items should be rendered') @@ -611,14 +610,14 @@ describe('CdkVirtualScrollViewport', () => { data.next([1, 2, 3]); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('newly emitted items should be rendered') .toEqual({start: 0, end: 3}); - }); + })); - it('should disconnect from data source on destroy', async () => { + it('should disconnect from data source on destroy', fakeAsync(() => { const data = new Subject(); const dataSource = new ArrayDataSource(data); @@ -626,69 +625,69 @@ describe('CdkVirtualScrollViewport', () => { spyOn(dataSource, 'disconnect').and.callThrough(); testComponent.items = dataSource as any; - await finishInit(fixture); + finishInit(fixture); expect(dataSource.connect).toHaveBeenCalled(); fixture.destroy(); expect(dataSource.disconnect).toHaveBeenCalled(); - }); + })); - it('should trackBy value by default', async () => { + it('should trackBy value by default', fakeAsync(() => { testComponent.items = []; spyOn(testComponent.virtualForOf._viewContainerRef, 'detach').and.callThrough(); - await finishInit(fixture); + finishInit(fixture); testComponent.items = [0]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.virtualForOf._viewContainerRef.detach).not.toHaveBeenCalled(); testComponent.items = [1]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.virtualForOf._viewContainerRef.detach).toHaveBeenCalled(); - }); + })); - it('should trackBy index when specified', async () => { + it('should trackBy index when specified', fakeAsync(() => { testComponent.trackBy = i => i; testComponent.items = []; spyOn(testComponent.virtualForOf._viewContainerRef, 'detach').and.callThrough(); - await finishInit(fixture); + finishInit(fixture); testComponent.items = [0]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.virtualForOf._viewContainerRef.detach).not.toHaveBeenCalled(); testComponent.items = [1]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.virtualForOf._viewContainerRef.detach).not.toHaveBeenCalled(); - }); + })); - it('should recycle views when template cache is large enough to accommodate', async () => { + it('should recycle views when template cache is large enough to accommodate', fakeAsync(() => { testComponent.trackBy = i => i; const spy = spyOn(testComponent.virtualForOf, '_getEmbeddedViewArgs').and.callThrough(); - await finishInit(fixture); + finishInit(fixture); // Should create views for the initial rendered items. expect(testComponent.virtualForOf._getEmbeddedViewArgs).toHaveBeenCalledTimes(4); spy.calls.reset(); - await triggerScroll(viewport, 10); + triggerScroll(viewport, 10); fixture.detectChanges(); - await fixture.whenStable(); + flush(); // As we first start to scroll we need to create one more item. This is because the first item // is still partially on screen and therefore can't be removed yet. At the same time a new @@ -699,30 +698,30 @@ describe('CdkVirtualScrollViewport', () => { const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 10; offset <= maxOffset; offset += 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); } // As we scroll through the rest of the items, no new views should be created, our existing 5 // can just be recycled as appropriate. expect(testComponent.virtualForOf._getEmbeddedViewArgs).not.toHaveBeenCalled(); - }); + })); - it('should not recycle views when template cache is full', async () => { + it('should not recycle views when template cache is full', fakeAsync(() => { testComponent.trackBy = i => i; testComponent.templateCacheSize = 0; const spy = spyOn(testComponent.virtualForOf, '_getEmbeddedViewArgs').and.callThrough(); - await finishInit(fixture); + finishInit(fixture); // Should create views for the initial rendered items. expect(testComponent.virtualForOf._getEmbeddedViewArgs).toHaveBeenCalledTimes(4); spy.calls.reset(); - await triggerScroll(viewport, 10); + triggerScroll(viewport, 10); fixture.detectChanges(); - await fixture.whenStable(); + flush(); // As we first start to scroll we need to create one more item. This is because the first item // is still partially on screen and therefore can't be removed yet. At the same time a new @@ -733,89 +732,93 @@ describe('CdkVirtualScrollViewport', () => { const maxOffset = testComponent.itemSize * testComponent.items.length - testComponent.viewportSize; for (let offset = 10; offset <= maxOffset; offset += 10) { - await triggerScroll(viewport, offset); + triggerScroll(viewport, offset); fixture.detectChanges(); - await fixture.whenStable(); + flush(); } // Since our template cache size is 0, as we scroll through the rest of the items, we need to // create a new view for each one. expect(testComponent.virtualForOf._getEmbeddedViewArgs).toHaveBeenCalledTimes(5); - }); + })); - it('should render up to maxBufferPx when buffer dips below minBufferPx', async () => { + it('should render up to maxBufferPx when buffer dips below minBufferPx', fakeAsync(() => { testComponent.minBufferPx = testComponent.itemSize; testComponent.maxBufferPx = testComponent.itemSize * 2; - await finishInit(fixture); + finishInit(fixture); expect(viewport.getRenderedRange()) .withContext('should have 2 buffer items initially') .toEqual({start: 0, end: 6}); - await triggerScroll(viewport, 50); + triggerScroll(viewport, 50); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('should not render additional buffer yet') .toEqual({start: 0, end: 6}); - await triggerScroll(viewport, 51); + triggerScroll(viewport, 51); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()) .withContext('should render 2 more buffer items') .toEqual({start: 0, end: 8}); - }); + })); - it('should throw if maxBufferPx is less than minBufferPx', async () => { - testComponent.minBufferPx = 100; - testComponent.maxBufferPx = 99; - await expectAsync(finishInit(fixture)).toBeRejectedWithError( + it('should throw if maxBufferPx is less than minBufferPx', fakeAsync(() => { + expect(() => { + testComponent.minBufferPx = 100; + testComponent.maxBufferPx = 99; + finishInit(fixture); + }).toThrowError( 'CDK virtual scroll: maxBufferPx must be greater than or equal to minBufferPx', ); - }); + })); - it('should register and degregister with ScrollDispatcher', async () => { + it('should register and degregister with ScrollDispatcher', fakeAsync(() => { const dispatcher = TestBed.inject(ScrollDispatcher); spyOn(dispatcher, 'register').and.callThrough(); spyOn(dispatcher, 'deregister').and.callThrough(); - await finishInit(fixture); + finishInit(fixture); expect(dispatcher.register).toHaveBeenCalledWith(testComponent.viewport.scrollable!); fixture.destroy(); expect(dispatcher.deregister).toHaveBeenCalledWith(testComponent.viewport.scrollable!); - }); + })); - it('should not throw when disposing of a view that will not fit in the cache', async () => { - await finishInit(fixture); + it('should not throw when disposing of a view that will not fit in the cache', fakeAsync(() => { + finishInit(fixture); testComponent.items = new Array(200).fill(0); testComponent.templateCacheSize = 1; // Reduce the cache size to something we can easily hit. fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - - for (let i = 0; i < 50; i++) { - viewport.scrollToIndex(i); - await triggerScroll(viewport); - fixture.detectChanges(); - await fixture.whenStable(); - } - }); + flush(); + + expect(() => { + for (let i = 0; i < 50; i++) { + viewport.scrollToIndex(i); + triggerScroll(viewport); + fixture.detectChanges(); + flush(); + } + }).not.toThrow(); + })); describe('viewChange change detection behavior', () => { - it('should not emit viewChange if there are no listeners', async () => { + it('should not emit viewChange if there are no listeners', fakeAsync(() => { const viewChangeSpy = spyOn(testComponent.virtualForOf.viewChange, 'next'); - await finishInit(fixture); + finishInit(fixture); testComponent.items = Array(10).fill(0); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); viewport.scrollToIndex(5); - await triggerScroll(viewport); + triggerScroll(viewport); expect(viewChangeSpy).not.toHaveBeenCalled(); - }); + })); }); }); @@ -836,95 +839,97 @@ describe('CdkVirtualScrollViewport', () => { ) as HTMLElement; }); - it('should initially be scrolled all the way right and showing the first item in horizontal mode', async () => { - testComponent.orientation = 'horizontal'; - await finishInit(fixture); - - expect(viewport.measureScrollOffset('right')).toBe(0); - expect(contentWrapperEl.style.transform).toMatch(/translateX\(0(px)?\)/); - expect((contentWrapperEl.children[0] as HTMLElement).innerText.trim()).toBe('0 - 0'); - }); + it( + 'should initially be scrolled all the way right and showing the first item in horizontal' + + ' mode', + fakeAsync(() => { + testComponent.orientation = 'horizontal'; + finishInit(fixture); + + expect(viewport.measureScrollOffset('right')).toBe(0); + expect(contentWrapperEl.style.transform).toMatch(/translateX\(0(px)?\)/); + expect((contentWrapperEl.children[0] as HTMLElement).innerText.trim()).toBe('0 - 0'); + }), + ); - it('should scroll through items as user scrolls to the left in horizontal mode', async () => { + it('should scroll through items as user scrolls to the left in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize * testComponent.items.length); + triggerScroll(viewport, testComponent.itemSize * testComponent.items.length); fixture.detectChanges(); - await fixture.whenStable(); - await wait(100); + flush(); expect(contentWrapperEl.style.transform).toBe('translateX(-300px)'); expect((contentWrapperEl.children[0] as HTMLElement).innerText.trim()).toBe('6 - 6'); - }); + })); - it('should interpret scrollToOffset amount as an offset from the right in horizontal mode', async () => { + it('should interpret scrollToOffset amount as an offset from the right in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.scrollToOffset(100); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset('right')).toBe(100); - }); + })); - it('should scroll to the correct index in horizontal mode', async () => { + it('should scroll to the correct index in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.scrollToIndex(2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect((contentWrapperEl.children[0] as HTMLElement).innerText.trim()).toBe('2 - 2'); - }); + })); - it('should emit the scrolled to index in horizontal mode', async () => { + it('should emit the scrolled to index in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); expect(testComponent.scrolledToIndex).toBe(0); viewport.scrollToIndex(2); - await triggerScroll(viewport); + triggerScroll(viewport); fixture.detectChanges(); - await fixture.whenStable(); - await wait(100); + flush(); expect(testComponent.scrolledToIndex).toBe(2); - }); + })); - it('should set total content size in RTL', async () => { - await finishInit(fixture); + it('should set total content size', fakeAsync(() => { + finishInit(fixture); viewport.setTotalContentSize(10000); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(viewport.elementRef.nativeElement.scrollHeight).toBe(10000); - }); + })); - it('should set total content size in horizontal mode in RTL', async () => { + it('should set total content size in horizontal mode', fakeAsync(() => { testComponent.orientation = 'horizontal'; - await finishInit(fixture); + finishInit(fixture); viewport.setTotalContentSize(10000); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(viewport.elementRef.nativeElement.scrollWidth).toBe(10000); - }); + })); }); describe('with no VirtualScrollStrategy', () => { - it('should fail on construction', async () => { + it('should fail on construction', fakeAsync(() => { expect(() => TestBed.createComponent(VirtualScrollWithNoStrategy)).toThrowError( 'Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.', ); - }); + })); }); describe('with item that injects ViewContainerRef', () => { @@ -941,8 +946,8 @@ describe('CdkVirtualScrollViewport', () => { it( 'should render the values in the correct sequence when an item is ' + 'injecting ViewContainerRef', - async () => { - await finishInit(fixture); + fakeAsync(() => { + finishInit(fixture); const contentWrapper = viewport.elementRef.nativeElement.querySelector( '.cdk-virtual-scroll-content-wrapper', @@ -951,7 +956,7 @@ describe('CdkVirtualScrollViewport', () => { expect(Array.from(contentWrapper.children).map(child => child.textContent!.trim())).toEqual( ['0', '1', '2', '3', '4', '5', '6', '7'], ); - }, + }), ); }); @@ -966,19 +971,19 @@ describe('CdkVirtualScrollViewport', () => { viewport = testComponent.viewport; })); - it('should call custom trackBy when virtual for is added after init', async () => { - await finishInit(fixture); + it('should call custom trackBy when virtual for is added after init', fakeAsync(() => { + finishInit(fixture); expect(testComponent.trackBy).not.toHaveBeenCalled(); testComponent.renderVirtualFor = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await triggerScroll(viewport, testComponent.itemSize * 5); + triggerScroll(viewport, testComponent.itemSize * 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(testComponent.trackBy).toHaveBeenCalled(); - }); + })); }); describe('with append only', () => { @@ -996,65 +1001,64 @@ describe('CdkVirtualScrollViewport', () => { ) as HTMLElement; })); - it('should not remove item that have already been rendered', async () => { - await finishInit(fixture); + it('should not remove item that have already been rendered', fakeAsync(() => { + finishInit(fixture); viewport.setRenderedRange({start: 100, end: 200}); fixture.detectChanges(); - await fixture.whenStable(); + flush(); viewport.setRenderedRange({start: 10, end: 50}); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getRenderedRange()).toEqual({start: 0, end: 200}); - }); + })); - it('rendered offset should always start at 0', async () => { - await finishInit(fixture); - await triggerScroll(viewport, testComponent.itemSize + 5); + it('rendered offset should always start at 0', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize + 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()) .withContext('should have 0px offset as we are using appendOnly') .toBe(0); - }); + })); - it('should set content offset to bottom of content with append only', async () => { - await finishInit(fixture); + it('should set content offset to bottom of content', fakeAsync(() => { + finishInit(fixture); const contentSize = viewport.measureRenderedContentSize(); expect(contentSize).toBeGreaterThan(0); viewport.setRenderedContentOffset(contentSize + 10, 'to-end'); - await fixture.whenStable(); - await new Promise(resolve => requestAnimationFrame(resolve)); // wait for afterNextRender + flush(); expect(viewport.getOffsetToRenderedContentStart()).toBe(0); - }); + })); - it('should set content offset to top of content with append only', async () => { - await finishInit(fixture); + it('should set content offset to top of content', fakeAsync(() => { + finishInit(fixture); viewport.setRenderedContentOffset(10, 'to-start'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.getOffsetToRenderedContentStart()).toBe(0); - }); + })); - it('should not set a transform when scrolling', async () => { - await finishInit(fixture); - await triggerScroll(viewport, 0); + it('should not set a transform when scrolling', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, 0); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(contentWrapperEl.style.transform).toBe('translateY(0px)'); - await triggerScroll(viewport, testComponent.itemSize * 10); + triggerScroll(viewport, testComponent.itemSize * 10); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(contentWrapperEl.style.transform).toBe('translateY(0px)'); - }); + })); }); describe('with custom scrolling element', () => { @@ -1068,24 +1072,24 @@ describe('CdkVirtualScrollViewport', () => { viewport = testComponent.viewport; }); - it('should measure viewport offset', async () => { - await finishInit(fixture); + it('should measure viewport offset', fakeAsync(() => { + finishInit(fixture); expect(viewport.measureViewportOffset('top')) .withContext('with scrolling-element padding-top: 50 offset should be 50') .toBe(50); - }); + })); - it('should measure scroll offset with custom scrolling element', async () => { - await finishInit(fixture); - await triggerScroll(viewport, 100); + it('should measure scroll offset', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, 100); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset('top')) .withContext('should be 50 (actual scroll offset - viewport offset)') .toBe(50); - }); + })); }); describe('with scrollable window', () => { @@ -1099,18 +1103,18 @@ describe('CdkVirtualScrollViewport', () => { viewport = testComponent.viewport; }); - it('should measure scroll offset with scrollable window', async () => { - await finishInit(fixture); + it('should measure scroll offset', fakeAsync(() => { + finishInit(fixture); viewport.scrollToOffset(100 + 8); // the +8 is due to a horizontal scrollbar dispatchFakeEvent(window, 'scroll', true); - await fixture.whenStable(); + tick(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(viewport.measureScrollOffset('top')) .withContext('should be 50 (actual scroll offset - viewport offset)') .toBe(50); - }); + })); }); it('should be able to query for a virtual scroll viewport as a CdkScrollable', () => { @@ -1122,28 +1126,28 @@ describe('CdkVirtualScrollViewport', () => { }); /** Finish initializing the virtual scroll component at the beginning of a test. */ -async function finishInit(fixture: ComponentFixture) { +function finishInit(fixture: ComponentFixture) { // On the first cycle we render and measure the viewport. fixture.detectChanges(); - await fixture.whenStable(); + flush(); // On the second cycle we render the items. fixture.detectChanges(); - await fixture.whenStable(); + flush(); // Flush the initial fake scroll event. - await new Promise(resolve => requestAnimationFrame(resolve)); - await fixture.whenStable(); + tick(16); // flush animation frame + flush(); fixture.detectChanges(); } /** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */ -async function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { +function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { if (offset !== undefined) { viewport.scrollToOffset(offset); } dispatchFakeEvent(viewport.scrollable!.getElementRef().nativeElement, 'scroll'); - await new Promise(resolve => setTimeout(resolve, 50)); + tick(16); // flush animation frame } @Component({ @@ -1186,7 +1190,6 @@ async function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FixedSizeVirtualScroll { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1252,7 +1255,6 @@ class FixedSizeVirtualScroll { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FixedSizeVirtualScrollWithRtlDirection { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1297,7 +1299,6 @@ class FixedSizeVirtualScrollWithRtlDirection { } `, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollWithNoStrategy { items = []; @@ -1337,7 +1338,6 @@ class InjectsViewContainer { `, encapsulation: ViewEncapsulation.None, imports: [InjectsViewContainer, ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollWithItemInjectingViewContainer { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1376,7 +1376,6 @@ class VirtualScrollWithItemInjectingViewContainer { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DelayedInitializationVirtualScroll { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1415,7 +1414,6 @@ class DelayedInitializationVirtualScroll { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollWithAppendOnly { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1458,7 +1456,6 @@ class VirtualScrollWithAppendOnly { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollWithCustomScrollingElement { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1500,7 +1497,6 @@ class VirtualScrollWithCustomScrollingElement { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollWithScrollableWindow { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -1513,7 +1509,6 @@ class VirtualScrollWithScrollableWindow { @Component({ template: '', imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VirtualScrollableQuery { @ViewChild(CdkScrollable) scrollable!: CdkScrollable; diff --git a/src/cdk/scrolling/virtual-scroll-viewport.ts b/src/cdk/scrolling/virtual-scroll-viewport.ts index f78192dd0390..27f8ae2ea710 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk/scrolling/virtual-scroll-viewport.ts @@ -12,6 +12,7 @@ import { afterNextRender, ApplicationRef, booleanAttribute, + ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, @@ -76,6 +77,7 @@ export const CDK_VIRTUAL_SCROLL_VIEWPORT = new InjectionToken { viewport = testComponent.viewport; }); - it('should emit on viewChange inside the Angular zone', async () => { + it('should emit on viewChange inside the Angular zone', fakeAsync(() => { const zoneTest = jasmine.createSpy('zone test'); testComponent.virtualForOf.viewChange.subscribe(() => zoneTest(NgZone.isInAngularZone())); - await finishInit(fixture); + finishInit(fixture); expect(zoneTest).toHaveBeenCalledWith(true); - }); + })); describe('viewChange change detection behavior', () => { - it('should run change detection if there are any viewChange listeners', async () => { + it('should run change detection if there are any viewChange listeners', fakeAsync(() => { testComponent.virtualForOf.viewChange.subscribe(); - await finishInit(fixture); + finishInit(fixture); testComponent.items = Array(10).fill(0); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const spy = jasmine.createSpy(); afterNextRender(spy, {injector: TestBed.inject(Injector)}); viewport.scrollToIndex(5); - await triggerScroll(viewport); + triggerScroll(viewport); expect(spy).toHaveBeenCalledTimes(1); - }); + })); }); }); }); @@ -99,7 +99,6 @@ describe('CdkVirtualScrollViewport Zone.js intergation', () => { `, encapsulation: ViewEncapsulation.None, imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FixedSizeVirtualScroll { @ViewChild(CdkVirtualScrollViewport, {static: true}) viewport!: CdkVirtualScrollViewport; @@ -131,29 +130,29 @@ class FixedSizeVirtualScroll { } /** Finish initializing the virtual scroll component at the beginning of a test. */ -async function finishInit(fixture: ComponentFixture) { +function finishInit(fixture: ComponentFixture) { // On the first cycle we render and measure the viewport. fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); // On the second cycle we render the items. fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); // Flush the initial fake scroll event. - await new Promise(resolve => requestAnimationFrame(resolve)); - await fixture.whenStable(); + animationFrameScheduler.flush(); + flush(); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); } /** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */ -async function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { +function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { if (offset !== undefined) { viewport.scrollToOffset(offset); } dispatchFakeEvent(viewport.scrollable.getElementRef().nativeElement, 'scroll'); - await new Promise(resolve => setTimeout(resolve, 16)); + animationFrameScheduler.flush(); } diff --git a/src/cdk/scrolling/virtual-scrollable-element.ts b/src/cdk/scrolling/virtual-scrollable-element.ts index d1f75dfa8816..72f08fbdc4d8 100644 --- a/src/cdk/scrolling/virtual-scrollable-element.ts +++ b/src/cdk/scrolling/virtual-scrollable-element.ts @@ -20,6 +20,12 @@ import {CdkVirtualScrollable, VIRTUAL_SCROLLABLE} from './virtual-scrollable'; }, }) export class CdkVirtualScrollableElement extends CdkVirtualScrollable { + constructor(...args: unknown[]); + + constructor() { + super(); + } + override measureBoundingClientRectWithScrollOffset( from: 'left' | 'top' | 'right' | 'bottom', ): number { diff --git a/src/cdk/scrolling/virtual-scrollable-window.ts b/src/cdk/scrolling/virtual-scrollable-window.ts index cd3da9a466d4..c7db8340cd6b 100644 --- a/src/cdk/scrolling/virtual-scrollable-window.ts +++ b/src/cdk/scrolling/virtual-scrollable-window.ts @@ -18,6 +18,8 @@ import {CdkVirtualScrollable, VIRTUAL_SCROLLABLE} from './virtual-scrollable'; providers: [{provide: VIRTUAL_SCROLLABLE, useExisting: CdkVirtualScrollableWindow}], }) export class CdkVirtualScrollableWindow extends CdkVirtualScrollable { + constructor(...args: unknown[]); + constructor() { super(); const document = inject(DOCUMENT); diff --git a/src/cdk/scrolling/virtual-scrollable.ts b/src/cdk/scrolling/virtual-scrollable.ts index 7dda88627d4f..78c3ceead357 100644 --- a/src/cdk/scrolling/virtual-scrollable.ts +++ b/src/cdk/scrolling/virtual-scrollable.ts @@ -16,6 +16,11 @@ export const VIRTUAL_SCROLLABLE = new InjectionToken('VIRT */ @Directive() export abstract class CdkVirtualScrollable extends CdkScrollable { + constructor(...args: unknown[]); + constructor() { + super(); + } + /** * Measure the viewport size for the provided orientation. * diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts index f8bf5dfd1a6c..f0709e3f9878 100644 --- a/src/cdk/stepper/step-header.ts +++ b/src/cdk/stepper/step-header.ts @@ -18,6 +18,9 @@ import {FocusableOption} from '../a11y'; export class CdkStepHeader implements FocusableOption { _elementRef = inject>(ElementRef); + constructor(...args: unknown[]); + constructor() {} + /** Focuses the step header. */ focus() { this._elementRef.nativeElement.focus(); diff --git a/src/cdk/stepper/step-label.ts b/src/cdk/stepper/step-label.ts index 0d3cbc41fa49..786294780e78 100644 --- a/src/cdk/stepper/step-label.ts +++ b/src/cdk/stepper/step-label.ts @@ -13,4 +13,7 @@ import {Directive, TemplateRef, inject} from '@angular/core'; }) export class CdkStepLabel { template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/cdk/stepper/stepper-button.ts b/src/cdk/stepper/stepper-button.ts index 864fa31a0789..ab6926caaf3d 100644 --- a/src/cdk/stepper/stepper-button.ts +++ b/src/cdk/stepper/stepper-button.ts @@ -23,6 +23,9 @@ export class CdkStepperNext { /** Type of the next button. Defaults to "submit" if not specified. */ @Input() type: string = 'submit'; + + constructor(...args: unknown[]); + constructor() {} } /** Button that moves to the previous step in a stepper workflow. */ @@ -38,4 +41,7 @@ export class CdkStepperPrevious { /** Type of the previous button. Defaults to "button" if not specified. */ @Input() type: string = 'button'; + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index da7b6563ddb9..259a897c1dba 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -11,6 +11,7 @@ import {Direction, Directionality} from '../bidi'; import {ENTER, hasModifierKey, SPACE} from '../keycodes'; import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, @@ -40,7 +41,6 @@ import { type NgForm, type FormGroupDirective, } from '@angular/forms'; -import type {Field} from '@angular/forms/signals'; import {_getFocusedElementPierceShadowDom} from '../platform'; import {Observable, of as observableOf, Subject} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; @@ -57,9 +57,6 @@ export type StepContentPositionState = 'previous' | 'current' | 'next'; /** Possible orientation of a stepper. */ export type StepperOrientation = 'horizontal' | 'vertical'; -/** Possible controls that can be assigned to a step. */ -export type StepControl = AbstractControl | Field; - /** Change event emitted on selection changes. */ export class StepperSelectionEvent { /** Index of the step now selected. */ @@ -110,6 +107,7 @@ export interface StepperOptions { exportAs: 'cdkStep', template: '', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkStep implements OnChanges { private _stepperOptions: StepperOptions; @@ -136,7 +134,7 @@ export class CdkStep implements OnChanges { @ViewChild(TemplateRef, {static: true}) content!: TemplateRef; /** The top level abstract control of the step. */ - @Input() stepControl!: StepControl; + @Input() stepControl!: AbstractControl; /** Whether user has attempted to move away from the step. */ get interacted(): boolean { @@ -199,7 +197,7 @@ export class CdkStep implements OnChanges { return override; } - return interacted && (!this.stepControl || isValid(this.stepControl)); + return interacted && (!this.stepControl || this.stepControl.valid); } set completed(value: boolean) { this._completedOverride.set(value); @@ -257,9 +255,11 @@ export class CdkStep implements OnChanges { private _customError = signal(null); private _getDefaultError() { - return this.interacted && !!this.stepControl && isInvalid(this.stepControl); + return this.interacted && !!this.stepControl?.invalid; } + constructor(...args: unknown[]); + constructor() { const stepperOptions = inject(STEPPER_GLOBAL_OPTIONS, {optional: true}); this._stepperOptions = stepperOptions ? stepperOptions : {}; @@ -288,7 +288,7 @@ export class CdkStep implements OnChanges { // want the form to be back to its initial state (see #29781). Submitted state is on the // individual directives, rather than the control, so we need to reset them ourselves. this._childForms?.forEach(form => form.resetForm?.()); - reset(this.stepControl); + this.stepControl.reset(); } } @@ -341,14 +341,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { private _sortedHeaders = new QueryList(); /** Whether the validity of previous steps should be checked or not. */ - @Input({transform: booleanAttribute}) - get linear(): boolean { - return this._linear(); - } - set linear(value: boolean) { - this._linear.set(value); - } - private _linear = signal(false); + @Input({transform: booleanAttribute}) linear: boolean = false; /** The index of the selected step. */ @Input({transform: numberAttribute}) @@ -411,6 +404,9 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { } private _orientation: StepperOrientation = 'horizontal'; + constructor(...args: unknown[]); + constructor() {} + ngAfterContentInit() { this._steps.changes .pipe(startWith(this._steps), takeUntil(this._destroyed)) @@ -597,7 +593,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { .some(step => { const control = step.stepControl; const isIncomplete = control - ? isInvalid(control) || isPending(control) || !step.interacted + ? control.invalid || control.pending || !step.interacted : !step.completed; return isIncomplete && !step.optional && !step._completedOverride(); }); @@ -622,29 +618,3 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy { return index > -1 && (!this.steps || index < this.steps.length); } } - -function isField(value: StepControl): value is Field { - return typeof value === 'function'; -} - -function isValid(control: StepControl): boolean { - return isField(control) ? control().valid() : control.valid; -} - -function isInvalid(control: StepControl): boolean { - // Note: it's a bit redundant to have both `isValid` and `isInvalid`. We need both, because - // some internal apps mock out `invalid` specifically so `!valid` won't hit the mock. - return isField(control) ? control().invalid() : control.invalid; -} - -function isPending(control: StepControl): boolean { - return isField(control) ? control().pending() : control.pending; -} - -function reset(control: StepControl): void { - if (isField(control)) { - control().reset(); - } else { - control.reset(); - } -} diff --git a/src/cdk/table/BUILD.bazel b/src/cdk/table/BUILD.bazel index 8dc9a1d7bc84..99d3ea1a0f05 100644 --- a/src/cdk/table/BUILD.bazel +++ b/src/cdk/table/BUILD.bazel @@ -16,6 +16,7 @@ ng_project( ), assets = [":table.css"], deps = [ + "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/rxjs", "//src:dev_mode_types", @@ -41,6 +42,7 @@ ng_project( ), deps = [ ":table", + "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", "//:node_modules/rxjs", diff --git a/src/cdk/table/cell.ts b/src/cdk/table/cell.ts index 5bea35e8cfa7..92b9c069760a 100644 --- a/src/cdk/table/cell.ts +++ b/src/cdk/table/cell.ts @@ -33,6 +33,9 @@ export interface CellDef { export class CdkCellDef implements CellDef { /** @docs-private */ template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -45,6 +48,9 @@ export class CdkCellDef implements CellDef { export class CdkHeaderCellDef implements CellDef { /** @docs-private */ template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -57,6 +63,9 @@ export class CdkHeaderCellDef implements CellDef { export class CdkFooterCellDef implements CellDef { /** @docs-private */ template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -131,6 +140,9 @@ export class CdkColumnDef implements CanStick { */ _columnCssClassName!: string[]; + constructor(...args: unknown[]); + constructor() {} + /** Whether the sticky state has changed. */ hasStickyChanged(): boolean { const hasStickyChanged = this._hasStickyChanged; @@ -187,6 +199,8 @@ export class BaseCdkCell { }, }) export class CdkHeaderCell extends BaseCdkCell { + constructor(...args: unknown[]); + constructor() { super(inject(CdkColumnDef), inject(ElementRef)); } @@ -200,6 +214,8 @@ export class CdkHeaderCell extends BaseCdkCell { }, }) export class CdkFooterCell extends BaseCdkCell { + constructor(...args: unknown[]); + constructor() { const columnDef = inject(CdkColumnDef); const elementRef = inject(ElementRef); @@ -221,6 +237,8 @@ export class CdkFooterCell extends BaseCdkCell { }, }) export class CdkCell extends BaseCdkCell { + constructor(...args: unknown[]); + constructor() { const columnDef = inject(CdkColumnDef); const elementRef = inject(ElementRef); diff --git a/src/cdk/table/row.ts b/src/cdk/table/row.ts index 34e9853c2c3f..1205a883aa30 100644 --- a/src/cdk/table/row.ts +++ b/src/cdk/table/row.ts @@ -48,7 +48,10 @@ export abstract class BaseRowDef implements OnChanges { /** Differ used to check if any changes were made to the columns. */ protected _columnsDiffer!: IterableDiffer; - ngOnChanges(changes: SimpleChanges): void { + constructor(...args: unknown[]); + constructor() {} + + ngOnChanges(changes: SimpleChanges): void { // Create a new columns differ if one does not yet exist. Initialize it based on initial value // of the columns property or an empty array if none is provided. if (!this._columnsDiffer) { @@ -105,9 +108,15 @@ export class CdkHeaderRowDef extends BaseRowDef implements CanStick, OnChanges { } private _sticky = false; + constructor(...args: unknown[]); + + constructor() { + super(inject>(TemplateRef), inject(IterableDiffers)); + } + // Prerender fails to recognize that ngOnChanges in a part of this class through inheritance. // Explicitly define it so that the method is called as part of the Angular lifecycle. - override ngOnChanges(changes: SimpleChanges): void { + override ngOnChanges(changes: SimpleChanges): void { super.ngOnChanges(changes); } @@ -150,9 +159,15 @@ export class CdkFooterRowDef extends BaseRowDef implements CanStick, OnChanges { } private _sticky = false; + constructor(...args: unknown[]); + + constructor() { + super(inject>(TemplateRef), inject(IterableDiffers)); + } + // Prerender fails to recognize that ngOnChanges in a part of this class through inheritance. // Explicitly define it so that the method is called as part of the Angular lifecycle. - override ngOnChanges(changes: SimpleChanges): void { + override ngOnChanges(changes: SimpleChanges): void { super.ngOnChanges(changes); } @@ -184,9 +199,6 @@ export class CdkFooterRowDef extends BaseRowDef implements CanStick, OnChanges { export class CdkRowDef extends BaseRowDef { _table? = inject(CDK_TABLE, {optional: true}); - // TODO(andrewseguin): Add an input for providing a switch function to determine - // if this template should be used. - /** * Function that should return true if this row template should be used for the provided index * and row data. If left undefined, this row will be considered the default row template to use @@ -194,6 +206,14 @@ export class CdkRowDef extends BaseRowDef { * For every row, there must be at least one when function that passes or an undefined to default. */ when!: (index: number, rowData: T) => boolean; + + constructor(...args: unknown[]); + + constructor() { + // TODO(andrewseguin): Add an input for providing a switch function to determine + // if this template should be used. + super(inject>(TemplateRef), inject(IterableDiffers)); + } } /** Context provided to the row cells when `multiTemplateDataRows` is false */ @@ -276,6 +296,8 @@ export class CdkCellOutlet implements OnDestroy { */ static mostRecentCellOutlet: CdkCellOutlet | null = null; + constructor(...args: unknown[]); + constructor() { CdkCellOutlet.mostRecentCellOutlet = this; } @@ -299,7 +321,7 @@ export class CdkCellOutlet implements OnDestroy { }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, imports: [CdkCellOutlet], }) @@ -315,7 +337,7 @@ export class CdkHeaderRow {} }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, imports: [CdkCellOutlet], }) @@ -331,7 +353,7 @@ export class CdkFooterRow {} }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, imports: [CdkCellOutlet], }) @@ -347,4 +369,7 @@ export class CdkNoDataRow { _contentClassNames = ['cdk-no-data-row', 'cdk-row']; _cellClassNames = ['cdk-cell', 'cdk-no-data-cell']; _cellSelector = 'td, cdk-cell, [cdk-cell], .cdk-cell'; + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts index d618b79523e6..2bc5582bab80 100644 --- a/src/cdk/table/table.spec.ts +++ b/src/cdk/table/table.spec.ts @@ -16,7 +16,14 @@ import { signal, } from '@angular/core'; import {By} from '@angular/platform-browser'; -import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {BehaviorSubject, Observable, combineLatest, of as observableOf} from 'rxjs'; import {map} from 'rxjs/operators'; import {CdkColumnDef} from './cell'; @@ -36,6 +43,7 @@ import { getTableUnknownColumnError, getTableUnknownDataSourceError, } from './table-errors'; +import {NgClass} from '@angular/common'; import {CdkVirtualScrollViewport, ScrollingModule} from '../scrolling'; import {dispatchFakeEvent} from '../testing/private'; @@ -324,7 +332,7 @@ describe('CdkTable', () => { }); }); - it('should render no rows when the data is null', () => { + it('should render no rows when the data is null', fakeAsync(() => { setupTableTestApp(NullDataCdkTableApp); fixture.detectChanges(); @@ -332,7 +340,7 @@ describe('CdkTable', () => { // Emits that the data rows are changed even when the result is empty. expect(component.contentChangedCount).toBe(1); - }); + })); it('should be able to render multiple header and footer rows', () => { setupTableTestApp(MultipleHeaderFooterRowsCdkTableApp); @@ -346,7 +354,7 @@ describe('CdkTable', () => { ]); }); - it('should be able to project a caption', () => { + it('should be able to project a caption', fakeAsync(() => { setupTableTestApp(NativeHtmlTableWithCaptionApp); fixture.detectChanges(); @@ -354,9 +362,9 @@ describe('CdkTable', () => { expect(caption).toBeTruthy(); expect(tableElement.firstElementChild).toBe(caption); - }); + })); - it('should be able to project colgroup and col', () => { + it('should be able to project colgroup and col', fakeAsync(() => { setupTableTestApp(NativeHtmlTableWithColgroupAndCol); fixture.detectChanges(); @@ -366,7 +374,7 @@ describe('CdkTable', () => { expect(colgroupsAndCols[0].childNodes[0]).toBe(colgroupsAndCols[1]); expect(colgroupsAndCols[2].parentNode!.nodeName.toLowerCase()).toBe('table'); expect(colgroupsAndCols.map(e => e.nodeName.toLowerCase())).toEqual(['colgroup', 'col', 'col']); - }); + })); it('should not throw if `renderRows` is called too early', () => { // Note that we don't call `detectChanges` here, because we're testing specifically @@ -496,7 +504,7 @@ describe('CdkTable', () => { expect(() => fixture.detectChanges()).toThrowError(getTableUnknownDataSourceError().message); }); - it('should render only the header if the data source is undefined', () => { + it('should throw an error if the data source is not valid', () => { component.dataSource = undefined; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -813,12 +821,13 @@ describe('CdkTable', () => { }).toThrowError(getTableMissingMatchingRowDefError(data[0]).message); })); - it('should fail when multiple rows match data without multiTemplateDataRows', () => { + it('should fail when multiple rows match data without multiTemplateDataRows', fakeAsync(() => { let whenFixture = TestBed.createComponent(WhenRowMultipleDefaultsCdkTableApp); expect(() => { whenFixture.detectChanges(); + flush(); }).toThrowError(getTableMultipleDefaultRowDefsError().message); - }); + })); describe('with multiTemplateDataRows', () => { it('should be able to render multiple rows per data object', () => { @@ -983,7 +992,7 @@ describe('CdkTable', () => { dataRows = getRows(tableElement); }); - it('should stick and unstick headers in flex layout', async () => { + it('should stick and unstick headers', waitForAsync(async () => { component.stickyHeaders = ['header-1', 'header-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1030,9 +1039,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick and unstick footers in flex layout', async () => { + it('should stick and unstick footers', waitForAsync(async () => { component.stickyFooters = ['footer-1', 'footer-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1079,9 +1088,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick the correct footer row', async () => { + it('should stick the correct footer row', waitForAsync(async () => { component.stickyFooters = ['footer-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1090,9 +1099,9 @@ describe('CdkTable', () => { expectStickyStyles(footerRows[2], '10', {bottom: '0px'}); expectStickyBorderClass(footerRows[2], {bottom: true}); expectNoStickyStyles([footerRows[0], footerRows[1]]); - }); + })); - it('should stick and unstick left columns in flex layout', async () => { + it('should stick and unstick left columns', waitForAsync(async () => { component.stickyStartColumns = ['column-1', 'column-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1160,9 +1169,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick and unstick right columns in flex layout', async () => { + it('should stick and unstick right columns', waitForAsync(async () => { component.stickyEndColumns = ['column-4', 'column-6']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1230,9 +1239,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should reverse directions for sticky columns in rtl', async () => { + it('should reverse directions for sticky columns in rtl', waitForAsync(async () => { component.dir = 'rtl'; component.stickyStartColumns = ['column-1', 'column-2']; component.stickyEndColumns = ['column-5', 'column-6']; @@ -1274,9 +1283,9 @@ describe('CdkTable', () => { expectStickyBorderClass(footerCells[4], {left: true}); expectStickyStyles(footerCells[5], '1', {left: '0px'}); expectStickyBorderClass(footerCells[5]); - }); + })); - it('should stick and unstick combination of sticky header, footer, and columns in flex layout', async () => { + it('should stick and unstick combination of sticky header, footer, and columns', waitForAsync(async () => { component.stickyHeaders = ['header-1']; component.stickyFooters = ['footer-3']; component.stickyStartColumns = ['column-1']; @@ -1355,7 +1364,7 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); }); describe('on native table layout', () => { @@ -1371,7 +1380,7 @@ describe('CdkTable', () => { dataRows = getRows(tableElement); }); - it('should stick and unstick headers in native layout', async () => { + it('should stick and unstick headers', waitForAsync(async () => { component.stickyHeaders = ['header-1', 'header-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1423,9 +1432,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick and unstick footers in native layout', async () => { + it('should stick and unstick footers', waitForAsync(async () => { component.stickyFooters = ['footer-1', 'footer-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1477,9 +1486,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick tfoot when all rows are stuck', async () => { + it('should stick tfoot when all rows are stuck', waitForAsync(async () => { const tfoot = tableElement.querySelector('tfoot'); component.stickyFooters = ['footer-1']; fixture.changeDetectorRef.markForCheck(); @@ -1499,9 +1508,9 @@ describe('CdkTable', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); expectNoStickyStyles([tfoot]); - }); + })); - it('should stick and unstick left columns in native layout', async () => { + it('should stick and unstick left columns', waitForAsync(async () => { component.stickyStartColumns = ['column-1', 'column-3']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1569,9 +1578,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick and unstick right columns in native layout', async () => { + it('should stick and unstick right columns', waitForAsync(async () => { component.stickyEndColumns = ['column-4', 'column-6']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -1639,9 +1648,9 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); - it('should stick and unstick combination of sticky header, footer, and columns in native layout', async () => { + it('should stick and unstick combination of sticky header, footer, and columns', waitForAsync(async () => { component.stickyHeaders = ['header-1']; component.stickyFooters = ['footer-3']; component.stickyStartColumns = ['column-1']; @@ -1730,7 +1739,7 @@ describe('CdkTable', () => { }); expect(component.mostRecentStickyColumnsUpdate).toEqual({sizes: []}); expect(component.mostRecentStickyEndColumnsUpdate).toEqual({sizes: []}); - }); + })); }); }); @@ -1998,20 +2007,19 @@ describe('CdkTable', () => { }); describe('virtual scrolling', () => { - async function createVirtualScroll(component: Type): Promise<{ + function createVirtualScroll(component: Type): { fixture: ComponentFixture; table: HTMLTableElement; - }> { + } { const fixture = TestBed.createComponent(component); // Init logic copied from the virtual scroll tests. fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); - await fixture.whenStable(); - - await new Promise(resolve => requestAnimationFrame(resolve)); - await fixture.whenStable(); + flush(); + tick(16); + flush(); fixture.detectChanges(); return { @@ -2020,38 +2028,38 @@ describe('CdkTable', () => { }; } - async function triggerScroll( + function triggerScroll( fixture: ComponentFixture<{viewport: CdkVirtualScrollViewport}>, offset: number, ) { const viewport = fixture.componentInstance.viewport; viewport.scrollToOffset(offset); dispatchFakeEvent(viewport.scrollable!.getElementRef().nativeElement, 'scroll'); - await new Promise(resolve => setTimeout(resolve, 50)); + tick(16); } - it('should not render the full data set when using virtual scrolling', async () => { - const {fixture, table} = await createVirtualScroll(TableWithVirtualScroll); + it('should not render the full data set when using virtual scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(fixture.componentInstance.dataSource.data.length).toBeGreaterThan(2000); expect(getRows(table).length).toBe(10); - }); + })); - it('should maintain a limited amount of data as the user is scrolling', async () => { - const {fixture, table} = await createVirtualScroll(TableWithVirtualScroll); + it('should maintain a limited amount of data as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(getRows(table).length).toBe(10); - await triggerScroll(fixture, 500); + triggerScroll(fixture, 500); expect(getRows(table).length).toBe(13); - await triggerScroll(fixture, 500); + triggerScroll(fixture, 500); expect(getRows(table).length).toBe(13); - await triggerScroll(fixture, 1000); + triggerScroll(fixture, 1000); expect(getRows(table).length).toBe(12); - }); + })); - it('should update the table data as the user is scrolling', async () => { - const {fixture, table} = await createVirtualScroll(TableWithVirtualScroll); + it('should update the table data as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expectTableToMatchContent(table, [ ['Column A', 'Column B', 'Column C'], @@ -2068,7 +2076,7 @@ describe('CdkTable', () => { ['Footer A', 'Footer B', 'Footer C'], ]); - await triggerScroll(fixture, 1000); + triggerScroll(fixture, 1000); expectTableToMatchContent(table, [ ['Column A', 'Column B', 'Column C'], @@ -2086,22 +2094,22 @@ describe('CdkTable', () => { ['a_29', 'b_29', 'c_29'], ['Footer A', 'Footer B', 'Footer C'], ]); - }); + })); - it('should update the position of sticky cells as the user is scrolling', async () => { - const {fixture, table} = await createVirtualScroll(TableWithVirtualScroll); + it('should update the position of sticky cells as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); const assertStickyOffsets = (position: number) => { getHeaderCells(table).forEach(cell => expect(cell.style.top).toBe(`${position * -1}px`)); getFooterCells(table).forEach(cell => expect(cell.style.bottom).toBe(`${position}px`)); }; assertStickyOffsets(0); - await triggerScroll(fixture, 1000); + triggerScroll(fixture, 1000); assertStickyOffsets(884); - }); + })); - it('should force tables with virtual scrolling to have a fixed layout', async () => { - const {fixture, table} = await createVirtualScroll(TableWithVirtualScroll); + it('should force tables with virtual scrolling to have a fixed layout', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(fixture.componentInstance.isFixedLayout()).toBe(true); expect(table.classList).toContain('cdk-table-fixed-layout'); @@ -2109,15 +2117,15 @@ describe('CdkTable', () => { fixture.detectChanges(); expect(table.classList).toContain('cdk-table-fixed-layout'); - }); + })); - it('should throw if multiple row templates are used with virtual scrolling', async () => { - await expectAsync( - createVirtualScroll(TableWithVirtualScrollAndMultipleDefinitions), - ).toBeRejectedWithError( + it('should throw if multiple row templates are used with virtual scrolling', fakeAsync(() => { + expect(() => { + createVirtualScroll(TableWithVirtualScrollAndMultipleDefinitions); + }).toThrowError( /Conditional row definitions via the `when` input are not supported when virtual scrolling is enabled/, ); - }); + })); }); }); @@ -2216,7 +2224,6 @@ class BooleanDataSource extends DataSource { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTableApp { dataSource = new FakeDataSource(); @@ -2249,7 +2256,6 @@ class SimpleCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTableWithDifferentDataInputsApp { dataSource: DataSource | Observable | TestData[] | any = null; @@ -2271,7 +2277,6 @@ class CdkTableWithDifferentDataInputsApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BooleanRowCdkTableApp { dataSource = new BooleanDataSource(); @@ -2291,7 +2296,6 @@ class BooleanRowCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NullDataCdkTableApp { dataSource = observableOf(null); @@ -2324,7 +2328,6 @@ class NullDataCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultipleHeaderFooterRowsCdkTableApp {} @@ -2378,7 +2381,6 @@ class MultipleHeaderFooterRowsCdkTableApp {} `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenRowCdkTableApp { multiTemplateDataRows = false; @@ -2455,7 +2457,6 @@ class WhenRowCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CoercedMultiTemplateDataRows extends WhenRowCdkTableApp {} @@ -2493,7 +2494,6 @@ class CoercedMultiTemplateDataRows extends WhenRowCdkTableApp {} `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenRowWithoutDefaultCdkTableApp { dataSource = new FakeDataSource(); @@ -2539,7 +2539,6 @@ class WhenRowWithoutDefaultCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenRowMultipleDefaultsCdkTableApp { dataSource = new FakeDataSource(); @@ -2562,7 +2561,6 @@ class WhenRowMultipleDefaultsCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DynamicDataSourceCdkTableApp { dataSource!: FakeDataSource; @@ -2589,7 +2587,6 @@ class DynamicDataSourceCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TrackByCdkTableApp { trackByStrategy: 'reference' | 'propertyA' | 'index' = 'reference'; @@ -2675,7 +2672,6 @@ class StickyPositioningListenerTest implements StickyPositioningListener { `, providers: [{provide: STICKY_POSITIONING_LISTENER, useExisting: StickyFlexLayoutCdkTableApp}], imports: [CdkTableModule, BidiModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StickyFlexLayoutCdkTableApp extends StickyPositioningListenerTest { dataSource = new FakeDataSource(); @@ -2733,7 +2729,6 @@ class StickyFlexLayoutCdkTableApp extends StickyPositioningListenerTest { `, providers: [{provide: STICKY_POSITIONING_LISTENER, useExisting: StickyNativeLayoutCdkTableApp}], imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StickyNativeLayoutCdkTableApp extends StickyPositioningListenerTest { dataSource = new FakeDataSource(); @@ -2766,7 +2761,6 @@ class StickyNativeLayoutCdkTableApp extends StickyPositioningListenerTest { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DynamicColumnDefinitionsCdkTableApp { dynamicColumns: any[] = []; @@ -2788,7 +2782,6 @@ class DynamicColumnDefinitionsCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CustomRoleCdkTableApp { dataSource = new FakeDataSource(); @@ -2810,7 +2803,6 @@ class CustomRoleCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CrazyColumnNameCdkTableApp { dataSource = new FakeDataSource(); @@ -2837,7 +2829,6 @@ class CrazyColumnNameCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DuplicateColumnDefNameCdkTableApp { dataSource = new FakeDataSource(); @@ -2856,7 +2847,6 @@ class DuplicateColumnDefNameCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingColumnDefCdkTableApp { dataSource = new FakeDataSource(); @@ -2875,7 +2865,6 @@ class MissingColumnDefCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingColumnDefAfterRenderCdkTableApp implements AfterViewInit { dataSource!: FakeDataSource; @@ -2900,7 +2889,6 @@ class MissingColumnDefAfterRenderCdkTableApp implements AfterViewInit { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingAllRowDefsCdkTableApp { dataSource = new FakeDataSource(); @@ -2920,7 +2908,6 @@ class MissingAllRowDefsCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingHeaderRowDefCdkTableApp { dataSource = new FakeDataSource(); @@ -2940,7 +2927,6 @@ class MissingHeaderRowDefCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingRowDefCdkTableApp { dataSource = new FakeDataSource(); @@ -2960,7 +2946,6 @@ class MissingRowDefCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingFooterRowDefCdkTableApp { dataSource = new FakeDataSource(); @@ -2979,7 +2964,6 @@ class MissingFooterRowDefCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class UndefinedColumnsCdkTableApp { undefinedColumns: string[] | undefined; @@ -2993,7 +2977,7 @@ class UndefinedColumnsCdkTableApp { Column A `, - imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, + imports: [CdkTableModule, NgClass], }) class RowContextCdkTableApp { dataSource = new FakeDataSource(); @@ -3042,7 +3025,6 @@ class RowContextCdkTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WrapperCdkTableApp implements AfterContentInit { @ContentChildren(CdkColumnDef, {descendants: false}) columnDefs!: QueryList; @@ -3087,7 +3069,6 @@ class WrapperCdkTableApp implements AfterContentInit { `, imports: [CdkTableModule, WrapperCdkTableApp], - changeDetection: ChangeDetectionStrategy.Eager, }) class OuterTableApp { dataSource = new FakeDataSource(); @@ -3127,7 +3108,6 @@ class OuterTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NativeHtmlTableApp { dataSource = new FakeDataSource(); @@ -3179,7 +3159,6 @@ class NativeHtmlTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedHtmlTableApp { dataSource = new FakeDataSource(); @@ -3208,7 +3187,6 @@ class NestedHtmlTableApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NativeTableWithNoHeaderOrFooterRows { dataSource = new FakeDataSource(); @@ -3231,7 +3209,6 @@ class NativeTableWithNoHeaderOrFooterRows { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NativeHtmlTableWithCaptionApp { dataSource = new FakeDataSource(); @@ -3261,7 +3238,6 @@ class NativeHtmlTableWithCaptionApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NativeHtmlTableWithColgroupAndCol { dataSource = new FakeDataSource(); @@ -3286,7 +3262,6 @@ class NativeHtmlTableWithColgroupAndCol { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TableWithIndirectDescendantDefs { dataSource = new FakeDataSource(); @@ -3318,6 +3293,7 @@ class TableWithIndirectDescendantDefs { `, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkTableModule], }) class NativeHtmlTableAppOnPush { @@ -3330,7 +3306,6 @@ class NativeHtmlTableAppOnPush { `, imports: [NativeHtmlTableAppOnPush], - changeDetection: ChangeDetectionStrategy.Eager, }) class WrapNativeHtmlTableAppOnPush { dataSource = new FakeDataSource(); @@ -3371,7 +3346,6 @@ class WrapNativeHtmlTableAppOnPush { overflow: auto; } `, - changeDetection: ChangeDetectionStrategy.Eager, }) class TableWithVirtualScroll { @ViewChild(CdkTable) table!: CdkTable; @@ -3399,7 +3373,6 @@ class TableWithVirtualScroll { `, imports: [CdkTableModule, ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TableWithVirtualScrollAndMultipleDefinitions extends TableWithVirtualScroll { predicate = () => true; diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 0818d1ef981b..b38a844e0a87 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -124,6 +124,8 @@ export class DataRowOutlet implements RowOutlet { viewContainer = inject(ViewContainerRef); elementRef = inject(ElementRef); + constructor(...args: unknown[]); + constructor() { const table = inject>(CDK_TABLE); table._rowOutlet = this; @@ -142,6 +144,8 @@ export class HeaderRowOutlet implements RowOutlet { viewContainer = inject(ViewContainerRef); elementRef = inject(ElementRef); + constructor(...args: unknown[]); + constructor() { const table = inject>(CDK_TABLE); table._headerRowOutlet = this; @@ -160,6 +164,8 @@ export class FooterRowOutlet implements RowOutlet { viewContainer = inject(ViewContainerRef); elementRef = inject(ElementRef); + constructor(...args: unknown[]); + constructor() { const table = inject>(CDK_TABLE); table._footerRowOutlet = this; @@ -179,6 +185,8 @@ export class NoDataRowOutlet implements RowOutlet { viewContainer = inject(ViewContainerRef); elementRef = inject(ElementRef); + constructor(...args: unknown[]); + constructor() { const table = inject>(CDK_TABLE); table._noDataRowOutlet = this; @@ -267,7 +275,7 @@ export interface RenderRow { // The view for `MatTable` consists entirely of templates declared in other views. As they are // declared elsewhere, they are checked when their declaration points are checked. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, providers: [ {provide: CDK_TABLE, useExisting: CdkTable}, // Prevent nested tables from seeing this table's StickyPositioningListener. @@ -634,10 +642,7 @@ export class CdkTable /** Row definition that will only be rendered if there's no data in the table. */ @ContentChild(CdkNoDataRow) _noDataRow!: CdkNoDataRow; - /** Returns the currently-rendered rows in the table. */ - get renderedRows(): readonly RenderRow[] { - return this._renderRows; - } + constructor(...args: unknown[]); constructor() { const role = inject(new HostAttributeToken('role'), {optional: true}); diff --git a/src/cdk/table/text-column.spec.ts b/src/cdk/table/text-column.spec.ts index aeff51db2645..15ec097b5855 100644 --- a/src/cdk/table/text-column.spec.ts +++ b/src/cdk/table/text-column.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import { @@ -66,6 +66,18 @@ describe('CdkTextColumn', () => { ]); }); + it('should allow for custom data accessor', () => { + component.dataAccessorA = (data: TestData) => data.propertyA + '!'; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expectTableToMatchContent(tableElement, [ + ['PropertyA', 'PropertyB', 'PropertyC'], + ['a_1!', 'b_1', 'c_1'], + ['a_2!', 'b_2', 'c_2'], + ]); + }); + it('should update values when data changes', () => { component.data = [ {propertyA: 'changed-a_1', propertyB: 'b_1', propertyC: 'c_1'}, @@ -149,7 +161,6 @@ interface TestData { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BasicTextColumnApp { displayedColumns = ['propertyA', 'propertyB', 'propertyC']; @@ -169,7 +180,6 @@ class BasicTextColumnApp { `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MissingTableApp {} @@ -183,6 +193,5 @@ class MissingTableApp {} `, imports: [CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TextColumnWithoutNameApp extends BasicTextColumnApp {} diff --git a/src/cdk/table/text-column.ts b/src/cdk/table/text-column.ts index cafa071b8752..a87ab57bddd2 100644 --- a/src/cdk/table/text-column.ts +++ b/src/cdk/table/text-column.ts @@ -52,7 +52,7 @@ import {TEXT_COLUMN_OPTIONS, TextColumnOptions} from './tokens'; // mean's the template in the table's view will not have the updated value (and in fact will cause // an ExpressionChangedAfterItHasBeenCheckedError). // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCellDef, CdkCell], }) export class CdkTextColumn implements OnDestroy, OnInit { @@ -111,6 +111,8 @@ export class CdkTextColumn implements OnDestroy, OnInit { */ @ViewChild(CdkHeaderCellDef, {static: true}) headerCell!: CdkHeaderCellDef; + constructor(...args: unknown[]); + constructor() { this._options = this._options || {}; } diff --git a/src/cdk/testing/private/fake-directionality.ts b/src/cdk/testing/private/fake-directionality.ts index 9bb30962d047..6fd155a3abc9 100644 --- a/src/cdk/testing/private/fake-directionality.ts +++ b/src/cdk/testing/private/fake-directionality.ts @@ -7,15 +7,14 @@ */ import {Direction, Directionality} from '@angular/cdk/bidi'; -import {EventEmitter, OnDestroy, Service, signal, WritableSignal} from '@angular/core'; +import {EventEmitter, signal, WritableSignal} from '@angular/core'; import {toObservable} from '@angular/core/rxjs-interop'; import {skip} from 'rxjs/operators'; // Note: ngOnDestroy not needed, but must include it to match the Directionality interface. // Implementing the interface ensures the fake stays in sync with the real API. // tslint:disable-next-line:no-undecorated-class-with-angular-features lifecycle-hook-interface -@Service() -class FakeDirectionality implements Directionality, OnDestroy { +class FakeDirectionality implements Directionality { readonly change: EventEmitter; get value(): Direction { diff --git a/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts b/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts index 189d4a952b0d..4ef2de119fe5 100644 --- a/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts +++ b/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts @@ -18,8 +18,8 @@ declare interface FrameworkStabilizer { (callback: (didWork: boolean) => void): void; } -type WindowWithStabiliziers = Window & - typeof globalThis & { +declare global { + interface Window { /** * These hooks are exposed by Angular to register a callback for when the application is stable * (no more pending tasks). @@ -28,7 +28,8 @@ type WindowWithStabiliziers = Window & * angular/angular/blob/main/packages/platform-browser/src/browser/testability.ts#L30-L49 */ frameworkStabilizers: FrameworkStabilizer[]; - }; + } +} /** Options to configure the environment. */ export interface WebDriverHarnessEnvironmentOptions { @@ -47,11 +48,9 @@ const defaultEnvironmentOptions: WebDriverHarnessEnvironmentOptions = { * and invokes the specified `callback` when the application is stable (no more pending tasks). */ function whenStable(callback: (didWork: boolean[]) => void): void { - Promise.all( - (window as WindowWithStabiliziers).frameworkStabilizers.map( - stabilizer => new Promise(stabilizer), - ), - ).then(callback); + Promise.all(window.frameworkStabilizers.map(stabilizer => new Promise(stabilizer))).then( + callback, + ); } /** @@ -59,7 +58,7 @@ function whenStable(callback: (didWork: boolean[]) => void): void { * bootstrapped yet. */ function isBootstrapped() { - return !!(window as WindowWithStabiliziers).frameworkStabilizers; + return !!window.frameworkStabilizers; } /** Waits for angular to be ready after the page load. */ diff --git a/src/cdk/testing/tests/test-main-component.ts b/src/cdk/testing/tests/test-main-component.ts index eba523cfe206..502bcbec3912 100644 --- a/src/cdk/testing/tests/test-main-component.ts +++ b/src/cdk/testing/tests/test-main-component.ts @@ -10,6 +10,7 @@ import {ENTER} from '../../keycodes'; import {_supportsShadowDom} from '../../platform'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -26,6 +27,7 @@ import {TestSubComponent} from './test-sub-component'; selector: 'test-main', templateUrl: 'test-main-component.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [TestShadowBoundary, TestSubComponent, FormsModule, ReactiveFormsModule], }) export class TestMainComponent implements OnDestroy { diff --git a/src/cdk/testing/tests/test-shadow-boundary.ts b/src/cdk/testing/tests/test-shadow-boundary.ts index 9dbf1b29f4a6..02b797e9ca94 100644 --- a/src/cdk/testing/tests/test-shadow-boundary.ts +++ b/src/cdk/testing/tests/test-shadow-boundary.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; @Component({ selector: 'test-sub-shadow-boundary', template: '
    Shadow 2
    ', + changeDetection: ChangeDetectionStrategy.OnPush, // tslint:disable-next-line:validate-decorators encapsulation: ViewEncapsulation.ShadowDom, }) @@ -22,6 +23,7 @@ export class TestSubShadowBoundary {}
    Shadow 1
    `, + changeDetection: ChangeDetectionStrategy.OnPush, // tslint:disable-next-line:validate-decorators encapsulation: ViewEncapsulation.ShadowDom, imports: [TestSubShadowBoundary], diff --git a/src/cdk/testing/tests/test-sub-component.ts b/src/cdk/testing/tests/test-sub-component.ts index d9fcc7e6fe3e..acf34ee9166d 100644 --- a/src/cdk/testing/tests/test-sub-component.ts +++ b/src/cdk/testing/tests/test-sub-component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, Input, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core'; @Component({ selector: 'test-sub', @@ -18,6 +18,7 @@ import {Component, Input, ViewEncapsulation} from '@angular/core'; } `, encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TestSubComponent { @Input() title = ''; diff --git a/src/cdk/text-field/autofill.spec.ts b/src/cdk/text-field/autofill.spec.ts index b30e44446fa5..b8f0e94ccecc 100644 --- a/src/cdk/text-field/autofill.spec.ts +++ b/src/cdk/text-field/autofill.spec.ts @@ -7,7 +7,7 @@ */ import {normalizePassiveListenerOptions} from '../platform'; -import {Component, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {EMPTY} from 'rxjs'; import {AutofillEvent, AutofillMonitor} from './autofill'; @@ -193,7 +193,6 @@ describe('cdkAutofill', () => { `, imports: [TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class Inputs { // Cast to `any` so we can stub out some methods in the tests. @@ -205,7 +204,6 @@ class Inputs { @Component({ template: ``, imports: [TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InputWithCdkAutofilled { // Cast to `any` so we can stub out some methods in the tests. diff --git a/src/cdk/text-field/autofill.ts b/src/cdk/text-field/autofill.ts index 85582e467682..5c2506e67fae 100644 --- a/src/cdk/text-field/autofill.ts +++ b/src/cdk/text-field/autofill.ts @@ -12,7 +12,7 @@ import { ElementRef, EventEmitter, inject, - Service, + Injectable, NgZone, OnDestroy, OnInit, @@ -46,7 +46,7 @@ const listenerOptions = {passive: true}; * Based on the following blog post: * https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7 */ -@Service() +@Injectable({providedIn: 'root'}) export class AutofillMonitor implements OnDestroy { private _platform = inject(Platform); private _ngZone = inject(NgZone); @@ -55,6 +55,9 @@ export class AutofillMonitor implements OnDestroy { private _styleLoader = inject(_CdkPrivateStyleLoader); private _monitoredElements = new Map(); + constructor(...args: unknown[]); + constructor() {} + /** * Monitor for changes in the autofill state of the given input element. * @param element The element to monitor. @@ -156,6 +159,9 @@ export class CdkAutofill implements OnDestroy, OnInit { /** Emits when the autofill state of the element changes. */ @Output() readonly cdkAutofill = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { this._autofillMonitor .monitor(this._elementRef) diff --git a/src/cdk/text-field/autofill.zone.spec.ts b/src/cdk/text-field/autofill.zone.spec.ts index f922d1aa7b68..cc0e305367f2 100644 --- a/src/cdk/text-field/autofill.zone.spec.ts +++ b/src/cdk/text-field/autofill.zone.spec.ts @@ -1,11 +1,4 @@ -import { - Component, - ElementRef, - NgZone, - ViewChild, - provideZoneChangeDetection, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ElementRef, NgZone, ViewChild, provideZoneChangeDetection} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {AutofillMonitor} from './autofill'; import {TextFieldModule} from './text-field-module'; @@ -52,7 +45,6 @@ describe('AutofillMonitor Zone.js integration', () => { `, imports: [TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class Inputs { // Cast to `any` so we can stub out some methods in the tests. diff --git a/src/cdk/text-field/autosize.spec.ts b/src/cdk/text-field/autosize.spec.ts index fe9a141d9c8a..4181fdd50f55 100644 --- a/src/cdk/text-field/autosize.spec.ts +++ b/src/cdk/text-field/autosize.spec.ts @@ -1,4 +1,4 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {FormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; @@ -388,7 +388,6 @@ const textareaStyleReset = ` #autosize="cdkTextareaAutosize" [placeholder]="placeholder">{{content}}`, styles: textareaStyleReset, imports: [FormsModule, TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AutosizeTextAreaWithContent { @ViewChild('autosize') autosize!: CdkTextareaAutosize; @@ -402,7 +401,6 @@ class AutosizeTextAreaWithContent { template: ``, styles: textareaStyleReset, imports: [FormsModule, TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AutosizeTextareaWithNgModel { model = ''; @@ -412,7 +410,6 @@ class AutosizeTextareaWithNgModel { template: ``, styles: textareaStyleReset, imports: [FormsModule, TextFieldModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AutosizeTextareaWithoutAutosize { content: string = ''; diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts index 4573a39b65c4..4dab985073e1 100644 --- a/src/cdk/text-field/autosize.ts +++ b/src/cdk/text-field/autosize.ts @@ -126,6 +126,8 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { private _isViewInited = false; + constructor(...args: unknown[]); + constructor() { const styleLoader = inject(_CdkPrivateStyleLoader); styleLoader.load(_CdkTextFieldStyleLoader); diff --git a/src/cdk/text-field/text-field-style-loader.ts b/src/cdk/text-field/text-field-style-loader.ts index 90757bba8eab..e5cce3327f12 100644 --- a/src/cdk/text-field/text-field-style-loader.ts +++ b/src/cdk/text-field/text-field-style-loader.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; /** Component used to load the structural styles of the text field. */ @Component({ template: '', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styleUrl: 'text-field-prebuilt.css', host: {'cdk-text-field-style-loader': ''}, diff --git a/src/cdk/tree/nested-node.ts b/src/cdk/tree/nested-node.ts index 3cc5b57e7713..978433b50338 100644 --- a/src/cdk/tree/nested-node.ts +++ b/src/cdk/tree/nested-node.ts @@ -58,6 +58,12 @@ export class CdkNestedTreeNode }) nodeOutlet!: QueryList; + constructor(...args: unknown[]); + + constructor() { + super(); + } + ngAfterContentInit() { this._dataDiffer = this._differs.find([]).create(this._tree.trackBy); this._tree diff --git a/src/cdk/tree/node.ts b/src/cdk/tree/node.ts index df2141fd6c21..738ba8627479 100644 --- a/src/cdk/tree/node.ts +++ b/src/cdk/tree/node.ts @@ -47,4 +47,7 @@ export class CdkTreeNodeDef { * default. */ when!: (index: number, nodeData: T) => boolean; + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/cdk/tree/outlet.ts b/src/cdk/tree/outlet.ts index 0bfdbdec6a06..02144dca8461 100644 --- a/src/cdk/tree/outlet.ts +++ b/src/cdk/tree/outlet.ts @@ -24,4 +24,7 @@ export const CDK_TREE_NODE_OUTLET_NODE = new InjectionToken<{}>('CDK_TREE_NODE_O export class CdkTreeNodeOutlet { viewContainer = inject(ViewContainerRef); _node? = inject(CDK_TREE_NODE_OUTLET_NODE, {optional: true}); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/cdk/tree/padding.ts b/src/cdk/tree/padding.ts index 50972e8a736b..ded80bb31ad1 100644 --- a/src/cdk/tree/padding.ts +++ b/src/cdk/tree/padding.ts @@ -60,6 +60,8 @@ export class CdkTreeNodePadding implements OnDestroy { } _indent: number = 40; + constructor(...args: unknown[]); + constructor() { this._setPadding(); this._dir?.change.pipe(takeUntil(this._destroyed)).subscribe(() => this._setPadding(true)); diff --git a/src/cdk/tree/toggle.ts b/src/cdk/tree/toggle.ts index a6f06fa14990..ddc7ca2abffe 100644 --- a/src/cdk/tree/toggle.ts +++ b/src/cdk/tree/toggle.ts @@ -16,9 +16,9 @@ import {CdkTree, CdkTreeNode} from './tree'; @Directive({ selector: '[cdkTreeNodeToggle]', host: { - '(click)': '_toggle($event)', - '(keydown.Enter)': '_toggle($event); $event.preventDefault();', - '(keydown.Space)': '_toggle($event); $event.preventDefault();', + '(click)': '_toggle(); $event.stopPropagation();', + '(keydown.Enter)': '_toggle(); $event.preventDefault();', + '(keydown.Space)': '_toggle(); $event.preventDefault();', 'tabindex': '-1', }, }) @@ -30,13 +30,14 @@ export class CdkTreeNodeToggle { @Input({alias: 'cdkTreeNodeToggleRecursive', transform: booleanAttribute}) recursive: boolean = false; + constructor(...args: unknown[]); + constructor() {} + // Toggle the expanded or collapsed state of this node. // // Focus this node with expanding or collapsing it. This ensures that the active node will always // be visible when expanding and collapsing. - _toggle(event: Event): void { - event.stopPropagation(); - + _toggle(): void { this.recursive ? this._tree.toggleDescendants(this._treeNode.data) : this._tree.toggle(this._treeNode.data); diff --git a/src/cdk/tree/tree-using-legacy-key-manager.spec.ts b/src/cdk/tree/tree-using-legacy-key-manager.spec.ts index 943f9bc115d3..1cadb181365d 100644 --- a/src/cdk/tree/tree-using-legacy-key-manager.spec.ts +++ b/src/cdk/tree/tree-using-legacy-key-manager.spec.ts @@ -1,11 +1,4 @@ -import { - Component, - ElementRef, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ElementRef, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {of} from 'rxjs'; import {CdkTreeModule} from './tree-module'; @@ -83,7 +76,6 @@ class MinimalTestData { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTreeApp { isExpandable = (node: MinimalTestData) => node.children.length > 0; diff --git a/src/cdk/tree/tree-with-tree-control.spec.ts b/src/cdk/tree/tree-with-tree-control.spec.ts index 7e2b9815610e..ce44de525a9f 100644 --- a/src/cdk/tree/tree-with-tree-control.spec.ts +++ b/src/cdk/tree/tree-with-tree-control.spec.ts @@ -14,7 +14,6 @@ import { ViewChildren, WritableSignal, ViewChild, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; @@ -86,7 +85,7 @@ describe('CdkTree with TreeControl', () => { }); describe('flat tree', () => { - describe('flat tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: SimpleCdkTreeApp; @@ -246,7 +245,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('flat tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: CdkTreeAppWithToggle; @@ -392,7 +391,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('flat tree with array data source', () => { + describe('with array data source', () => { let fixture: ComponentFixture; let component: ArrayDataSourceCdkTreeApp; @@ -438,7 +437,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('flat tree with observable data source', () => { + describe('with observable data source', () => { let fixture: ComponentFixture; let component: ObservableDataSourceCdkTreeApp; @@ -484,7 +483,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('flat tree with trackBy', () => { + describe('with trackBy', () => { let fixture: ComponentFixture; let component: CdkTreeAppWithTrackBy; @@ -586,7 +585,7 @@ describe('CdkTree with TreeControl', () => { }); describe('nested tree', () => { - describe('nested tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: NestedCdkTreeApp; @@ -767,7 +766,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('nested tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: NestedCdkTreeAppWithToggle; @@ -912,7 +911,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('nested tree with array data source', () => { + describe('with array data source', () => { let fixture: ComponentFixture; let component: ArrayDataSourceNestedCdkTreeApp; @@ -951,7 +950,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('nested tree with observable data source', () => { + describe('with observable data source', () => { let fixture: ComponentFixture; let component: ObservableDataSourceNestedCdkTreeApp; @@ -990,7 +989,7 @@ describe('CdkTree with TreeControl', () => { }); }); - describe('nested tree with trackBy', () => { + describe('with trackBy', () => { let fixture: ComponentFixture; let component: NestedCdkTreeAppWithTrackBy; @@ -1443,7 +1442,6 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -1470,7 +1468,6 @@ class SimpleCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTreeAppWithIndirectNodes extends SimpleCdkTreeApp {} @@ -1484,7 +1481,6 @@ class SimpleCdkTreeAppWithIndirectNodes extends SimpleCdkTreeApp {} `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1509,7 +1505,6 @@ class NestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StaticNestedCdkTreeApp { getChildren = (node: TestData) => node.children; @@ -1547,7 +1542,6 @@ class StaticNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeNestedCdkTreeApp { isSecondNode = (_: number, node: TestData) => node.pizzaBase.indexOf('2') > 0; @@ -1572,7 +1566,6 @@ class WhenNodeNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1600,7 +1593,6 @@ class CdkTreeAppWithToggle { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1631,7 +1623,6 @@ class NestedCdkTreeAppWithToggle { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeCdkTreeApp { isOddNode = (_: number, node: TestData) => node.level % 2 === 1; @@ -1656,7 +1647,6 @@ class WhenNodeCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArrayDataSourceCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -1684,7 +1674,6 @@ class ArrayDataSourceCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ObservableDataSourceCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -1711,7 +1700,6 @@ class ObservableDataSourceCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArrayDataSourceNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1737,7 +1725,6 @@ class ArrayDataSourceNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ObservableDataSourceNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1764,7 +1751,6 @@ class ObservableDataSourceNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DepthNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1789,7 +1775,6 @@ class DepthNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTreeAppWithTrackBy { trackByStrategy: 'reference' | 'property' | 'index' = 'reference'; @@ -1824,7 +1809,6 @@ class CdkTreeAppWithTrackBy { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeAppWithTrackBy { trackByStrategy: 'reference' | 'property' | 'index' = 'reference'; diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index 014e897cd72e..14efb920c981 100644 --- a/src/cdk/tree/tree.spec.ts +++ b/src/cdk/tree/tree.spec.ts @@ -17,7 +17,6 @@ import { WritableSignal, inject, signal, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {AsyncPipe} from '@angular/common'; @@ -140,7 +139,7 @@ describe('CdkTree', () => { expect(ariaLevels).toEqual(['2', '3', '2', '2']); }); - it('should render flat tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { // add a child to the first node let data = dataSource.data; dataSource.addChild(data[2]); @@ -252,7 +251,7 @@ describe('CdkTree', () => { }); }); - describe('flat tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: CdkTreeAppWithToggle; @@ -332,7 +331,7 @@ describe('CdkTree', () => { .toEqual(['0', '-1']); }); - it('should expand/collapse the node recursively in flat tree', () => { + it('should expand/collapse the node recursively', () => { expect(dataSource.data.length).toBe(3); expect(getExpandedNodes(component.dataSource?.getRecursiveData(), component.tree).length) @@ -414,7 +413,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render flat tree with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -445,7 +444,7 @@ describe('CdkTree', () => { }); }); - describe('flat tree with array data source', () => { + describe('with array data source', () => { let fixture: ComponentFixture; let component: ArrayDataSourceCdkTreeApp; @@ -459,7 +458,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render flat tree with array data source with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -490,7 +489,7 @@ describe('CdkTree', () => { }); }); - describe('flat tree with observable data source', () => { + describe('with observable data source', () => { let fixture: ComponentFixture; let component: ObservableDataSourceCdkTreeApp; @@ -505,7 +504,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render flat tree with observable data source with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -536,7 +535,7 @@ describe('CdkTree', () => { }); }); - describe('flat tree with trackBy', () => { + describe('with trackBy', () => { let fixture: ComponentFixture; let component: CdkTreeAppWithTrackBy; @@ -683,7 +682,7 @@ describe('CdkTree', () => { expect(nodes[0].classList).toContain('customNodeClass'); }); - it('should render nested tree with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -774,7 +773,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render nested tree with static children with the right data', () => { + it('with the right data', () => { expectNestedTreeToMatch( treeElement, [`topping_1 - cheese_1 + base_1`], @@ -801,7 +800,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render nested tree with when node with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -828,7 +827,7 @@ describe('CdkTree', () => { }); }); - describe('nested tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: NestedCdkTreeAppWithToggle; @@ -842,7 +841,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render nested tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { expect(getNodes(treeElement).map(x => x.getAttribute('aria-expanded'))) .withContext('aria-expanded attributes') .toEqual([null, null, null]); @@ -924,7 +923,7 @@ describe('CdkTree', () => { ); }); - it('should expand/collapse the node recursively in nested tree', () => { + it('should expand/collapse the node recursively', () => { fixture.changeDetectorRef.markForCheck(); let data = dataSource.data; const child = dataSource.addChild(data[1], false); @@ -966,37 +965,9 @@ describe('CdkTree', () => { [`topping_3 - cheese_3 + base_3`], ); }); - - it('should not collapse parent when child is toggled via keyboard', () => { - component.toggleRecursively = false; - fixture.changeDetectorRef.markForCheck(); - let data = dataSource.data; - const child = dataSource.addChild(data[1], false); - dataSource.addChild(child, false); - fixture.detectChanges(); - - // Expand parent - (getNodes(treeElement)[1] as HTMLElement).click(); - fixture.detectChanges(); - - expect(component.tree.isExpanded(data[1])).toBe(true); - - // Focus child node (which is now at index 2) - const childNode = getNodes(treeElement)[2] as HTMLElement; - - // Simulate Enter key on child node - const event = createKeyboardEvent('keydown', undefined, 'Enter'); - childNode.dispatchEvent(event); - fixture.detectChanges(); - - // Verify parent is still expanded - expect(component.tree.isExpanded(data[1])) - .withContext('Parent should remain expanded') - .toBe(true); - }); }); - describe('nested tree with array data source', () => { + describe('with array data source', () => { let fixture: ComponentFixture; let component: ArrayDataSourceNestedCdkTreeApp; @@ -1010,7 +981,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render nested tree with array data source with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -1035,7 +1006,7 @@ describe('CdkTree', () => { }); }); - describe('nested tree with observable data source', () => { + describe('with observable data source', () => { let fixture: ComponentFixture; let component: ObservableDataSourceNestedCdkTreeApp; @@ -1049,7 +1020,7 @@ describe('CdkTree', () => { treeElement = fixture.nativeElement.querySelector('cdk-tree'); }); - it('should render nested tree with observable data source with the right data', () => { + it('with the right data', () => { expect(dataSource.data.length).toBe(3); let data = dataSource.data; @@ -1074,7 +1045,7 @@ describe('CdkTree', () => { }); }); - describe('nested tree with trackBy', () => { + describe('with trackBy', () => { let fixture: ComponentFixture; let component: NestedCdkTreeAppWithTrackBy; @@ -1406,7 +1377,7 @@ describe('CdkTree', () => { component = fixture.componentInstance; }); - describe(`when pressing 'b' with default configuration`, () => { + describe(`when pressing 'b'`, () => { beforeEach(fakeAsync(() => { component.tree.nativeElement.dispatchEvent( createKeyboardEvent('keydown', undefined, 'b'), @@ -1434,7 +1405,7 @@ describe('CdkTree', () => { component = fixture.componentInstance; }); - describe(`when pressing 'b' with typeahead label binding`, () => { + describe(`when pressing 'b'`, () => { beforeEach(fakeAsync(() => { component.tree.nativeElement.dispatchEvent( createKeyboardEvent('keydown', undefined, 'b'), @@ -1729,7 +1700,6 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: any[]) { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -1758,7 +1728,6 @@ class SimpleCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleCdkTreeAppWithIndirectNodes extends SimpleCdkTreeApp {} @@ -1773,7 +1742,6 @@ class SimpleCdkTreeAppWithIndirectNodes extends SimpleCdkTreeApp {} `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1798,7 +1766,6 @@ class NestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StaticNestedCdkTreeApp { getChildren = (node: TestData) => node.children; @@ -1833,7 +1800,6 @@ class StaticNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeNestedCdkTreeApp { isSecondNode = (_: number, node: TestData) => node.pizzaBase.indexOf('2') > 0; @@ -1858,7 +1824,6 @@ class WhenNodeNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1889,7 +1854,6 @@ class CdkTreeAppWithToggle { `, imports: [CdkTreeModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1922,7 +1886,6 @@ class NestedCdkTreeAppWithToggle { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeCdkTreeApp { isOddNode = (_: number, node: TestData) => node.level % 2 === 1; @@ -1947,7 +1910,6 @@ class WhenNodeCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArrayDataSourceCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -1983,7 +1945,6 @@ class ArrayDataSourceCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ObservableDataSourceCdkTreeApp { getLevel = (node: TestData) => node.level; @@ -2009,7 +1970,6 @@ class ObservableDataSourceCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArrayDataSourceNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -2034,7 +1994,6 @@ class ArrayDataSourceNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ObservableDataSourceNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -2060,7 +2019,6 @@ class ObservableDataSourceNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DepthNestedCdkTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -2084,7 +2042,6 @@ class DepthNestedCdkTreeApp { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CdkTreeAppWithTrackBy { trackByStrategy: 'reference' | 'property' | 'index' = 'reference'; @@ -2119,7 +2076,6 @@ class CdkTreeAppWithTrackBy { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedCdkTreeAppWithTrackBy { trackByStrategy: 'reference' | 'property' | 'index' = 'reference'; @@ -2164,7 +2120,6 @@ class MinimalTestData { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TypeaheadLabelFlatTreeWithThreeNodes { isExpandable = (node: MinimalTestData) => node.children.length > 0; @@ -2189,7 +2144,6 @@ class TypeaheadLabelFlatTreeWithThreeNodes { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FlatTreeWithThreeNodes { isExpandable = (node: MinimalTestData) => node.children.length > 0; @@ -2217,7 +2171,6 @@ class FlatTreeWithThreeNodes { `, imports: [CdkTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class IsExpandableOrderingTest { getChildren = (node: MinimalTestData) => node.children; diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index bd3a444d046b..95913a582078 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -112,7 +112,7 @@ type RenderingData = // The view for `CdkTree` consists entirely of templates declared in other views. As they are // declared elsewhere, they are checked when their declaration points are checked. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [CdkTreeNodeOutlet], }) export class CdkTree @@ -267,6 +267,9 @@ export class CdkTree _keyManager!: TreeKeyManagerStrategy>; private _viewInit = false; + constructor(...args: unknown[]); + constructor() {} + ngAfterContentInit() { this._initializeKeyManager(); } @@ -1261,7 +1264,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI /** * The text used to locate this item during typeahead. If not specified, the `textContent` will - * be used. + * will be used. */ @Input('cdkTreeNodeTypeaheadLabel') typeaheadLabel: string | null = null; @@ -1385,6 +1388,8 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI private _changeDetectorRef = inject(ChangeDetectorRef); + constructor(...args: unknown[]); + constructor() { CdkTreeNode.mostRecentTreeNode = this as CdkTreeNode; } diff --git a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html index 043475d7a2d3..212a917e6252 100644 --- a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html +++ b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html @@ -13,25 +13,75 @@ [softDisabled]="softDisabled.value" [wrap]="wrap.value" > - @for (item of items(); track item.panelId) { -
    -

    - -

    -
    - -

    {{item.content}}

    - -
    -
    -
    - } +
    +

    + +

    +
    + +

    This is the content for Item 1.

    + +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 2.

    + +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 3.

    +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 4

    +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 5

    +
    +
    +
    diff --git a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts index d0c2370bd394..039c82777309 100644 --- a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts +++ b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -37,41 +37,17 @@ export class AccordionConfigurableExample { disabled = new FormControl(false, {nonNullable: true}); softDisabled = new FormControl(true, {nonNullable: true}); - items = signal([ - { - panelId: 'item-1', - header: 'Item 1 Trigger', - content: 'This is the content for Item 1.', - disabled: signal(false), - expanded: signal(false), - }, - { - panelId: 'item-2', - header: 'Item 2 Trigger (disabled)', - content: 'This is the content for Item 2.', - disabled: signal(true), - expanded: signal(false), - }, - { - panelId: 'item-3', - header: 'Item 3 Trigger', - content: 'This is the content for Item 3.', - disabled: signal(false), - expanded: signal(false), - }, - { - panelId: 'item-4', - header: 'Item 4 Trigger', - content: 'This is the content for Item 4.', - disabled: signal(false), - expanded: signal(false), - }, - { - panelId: 'item-5', - header: 'Item 5 Trigger', - content: 'This is the content for Item 5.', - disabled: signal(false), - expanded: signal(false), - }, - ]); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + // Example items + items = ['item1', 'item2', 'item3', 'item4', 'item5']; + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); + } } diff --git a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html index c52f35989803..abc529f832c8 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html @@ -6,12 +6,12 @@ >

    -

    -
    +

    This is the content for Item 1.

    @@ -20,12 +20,12 @@

    -

    -
    +

    This is the content for Item 2. This should not be expandable if trigger is disabled.

    @@ -34,12 +34,12 @@

    -

    -
    +

    This is the content for Item 3.

    @@ -48,12 +48,12 @@

    -

    -
    +

    This is the content for Item 4

    @@ -62,12 +62,12 @@

    -

    -
    +

    This is the content for Item 5

    diff --git a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts index 17cdb83c7b0f..2194152013e0 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,14 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledFocusableExample { - expansionIcon(panel: AccordionPanel): string { - return panel.visible() ? 'expand_less' : 'expand_more'; + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); } } diff --git a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html index e520421ce898..3b4a6bdd8f0f 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html @@ -6,12 +6,12 @@ >

    -

    -
    +

    This is the content for Item 1.

    @@ -20,12 +20,12 @@

    -

    -
    +

    This is the content for Item 2. This should not be reachable or expandable.

    @@ -34,12 +34,12 @@

    -

    -
    +

    This is the content for Item 3.

    @@ -48,12 +48,12 @@

    -

    -
    +

    This is the content for Item 4

    @@ -62,12 +62,12 @@

    -

    -
    +

    This is the content for Item 5

    diff --git a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts index 976d32e46b4a..696db21568d1 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,14 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledSkippedExample { - expansionIcon(panel: AccordionPanel): string { - return panel.visible() ? 'expand_less' : 'expand_more'; + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); } } diff --git a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html index 77b205ffbe10..572a4d5ca697 100644 --- a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html @@ -1,12 +1,12 @@

    -

    -
    +

    This is the content for Item 1.

    @@ -16,12 +16,12 @@

    -

    -
    +

    This is the content for Item 2.

    @@ -31,12 +31,12 @@

    -

    -
    +

    This is the content for Item 3.

    @@ -45,12 +45,12 @@

    -

    -
    +

    This is the content for Item 4

    @@ -59,12 +59,12 @@

    -

    -
    +

    This is the content for Item 5

    diff --git a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts index 3862a1fe110f..d668c9be4e43 100644 --- a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,14 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledExample { - expansionIcon(panel: AccordionPanel): string { - return panel.visible() ? 'expand_less' : 'expand_more'; + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); } } diff --git a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html index 28c4ddbb2372..b657bce20ff5 100644 --- a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html +++ b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html @@ -5,12 +5,12 @@ >

    -

    -
    +

    This is the content for Item 1. Multiple items can be expanded.

    @@ -20,12 +20,12 @@

    -

    -
    +

    This is the content for Item 2.

    @@ -35,13 +35,14 @@

    -

    @@ -52,13 +53,14 @@

    -

    @@ -69,13 +71,14 @@

    -

    diff --git a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts index 24e49f64d5de..41d0b82cde32 100644 --- a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts +++ b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,14 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionMultiExpansionExample { - expansionIcon(panel: AccordionPanel): string { - return panel.visible() ? 'expand_less' : 'expand_more'; + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); } } diff --git a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html index 2e8d80885821..fca03e697d10 100644 --- a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html +++ b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html @@ -5,12 +5,12 @@ >

    -

    -
    +

    This is the content for Item 1.

    @@ -20,12 +20,12 @@

    -

    -
    +

    This is the content for Item 2.

    @@ -35,14 +35,14 @@

    -

    @@ -53,14 +53,14 @@

    -

    @@ -71,14 +71,14 @@

    -

    diff --git a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts index 17cb542ec90d..a9558c406eb3 100644 --- a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts +++ b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,14 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionSingleExpansionExample { - expansionIcon(panel: AccordionPanel): string { - return panel.visible() ? 'expand_less' : 'expand_more'; + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); + + expansionIcon(item: string): Signal { + return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); } } diff --git a/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html b/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html new file mode 100644 index 000000000000..c2dcc08d4ca7 --- /dev/null +++ b/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html @@ -0,0 +1,109 @@ +
    + Wrap (ArrowKey-only) + Multi + Disabled + Soft Disabled + + + Expanded Items + + @for (item of items; track item) { + {{item}} + } + + +
    + +
    +
    +

    + +

    +
    + +

    This is the content for Item 1.

    + +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 2.

    + +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 3.

    +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 4

    +
    +
    +
    + +
    +

    + +

    +
    + +

    This is the content for Item 5

    +
    +
    +
    +
    diff --git a/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.html b/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.html index 8d48bffe1760..0f936ab4c3e6 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.html +++ b/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.html @@ -1,14 +1,10 @@ -
    +
    search
    -
    - {{countries().length === 0 ? 'No results found for ' + query() : ''}} +
    + {{countries().length === 0 ? 'No results found' : ''}}
    - - + +
    @if (countries().length === 0) {
    No results found
    } -
    +
    @for (country of countries(); track country) {
    {{country}} diff --git a/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.ts b/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.ts index 45a025a8e471..85954d71424c 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.ts +++ b/src/components-examples/aria/autocomplete/autocomplete-auto-select/autocomplete-auto-select-example.ts @@ -6,10 +6,23 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; import {COUNTRIES} from '../countries'; +import {CdkAriaLive} from '@angular/cdk/a11y'; import {OverlayModule} from '@angular/cdk/overlay'; import {FormsModule} from '@angular/forms'; @@ -18,19 +31,34 @@ import {FormsModule} from '@angular/forms'; selector: 'autocomplete-auto-select-example', templateUrl: 'autocomplete-auto-select-example.html', styleUrl: '../autocomplete.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], + imports: [ + CdkAriaLive, + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteAutoSelectExample { /** The selected value of the combobox. */ - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); + listbox = viewChild>(Listbox); - popupExpanded = signal(false); - searchString = signal(''); - selectedOption = signal([]); + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** A reference to the ng aria combobox input. */ + comboboxInput = viewChild(ComboboxInput); /** The query string used to filter the list of countries. */ - query = computed(() => this.searchString()); + query = computed(() => this.comboboxInput()?.value() || ''); /** The list of countries filtered by the query. */ countries = computed(() => @@ -38,31 +66,26 @@ export class AutocompleteAutoSelectExample { ); constructor() { + // Scrolls to the active item when the active option changes. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + if (this.combobox()?.expanded()) { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); + } }); } /** Clears the query and the listbox value. */ clear(): void { - this.searchString.set(''); - this.selectedOption.set([]); - } - - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - } - this.popupExpanded.set(false); - this.combobox()?.element.focus(); + this.comboboxInput()?.value.set(''); + this.listbox?.()?.values.set([]); } /** Handles keydown events on the clear button. */ onKeydown(event: KeyboardEvent): void { if (event.key === 'Enter') { this.clear(); - this.popupExpanded.set(false); + this.combobox?.()?.close(); event.stopPropagation(); } } diff --git a/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.html b/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.html index 9c2980ee0a36..f370bc8906b7 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.html +++ b/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.html @@ -1,26 +1,24 @@ -
    +
    search
    - - + +
    @if (countries().length === 0) {
    No results found
    } -
    +
    @for (country of countries(); track country) {
    {{country}} diff --git a/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.ts b/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.ts index 26f051d80313..ed590c7f3411 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.ts +++ b/src/components-examples/aria/autocomplete/autocomplete-disabled/autocomplete-disabled-example.ts @@ -6,9 +6,22 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + signal, + viewChild, + viewChildren, +} from '@angular/core'; import {COUNTRIES} from '../countries'; import {OverlayModule} from '@angular/cdk/overlay'; import {FormsModule} from '@angular/forms'; @@ -18,19 +31,27 @@ import {FormsModule} from '@angular/forms'; selector: 'autocomplete-disabled-example', templateUrl: 'autocomplete-disabled-example.html', styleUrl: '../autocomplete.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteDisabledExample { - /** The selected value of the combobox. */ - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); + /** The options available in the listbox. */ + options = viewChildren>(Option); - popupExpanded = signal(false); - searchString = signal('Select a country'); - selectedOption = signal([]); + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); /** The query string used to filter the list of countries. */ - query = computed(() => this.searchString()); + query = signal('United States of America'); /** The list of countries filtered by the query. */ countries = computed(() => @@ -38,8 +59,12 @@ export class AutocompleteDisabledExample { ); constructor() { + // Scrolls to the active item when the active option changes. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + if (this.combobox()?.expanded()) { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); + } }); } } diff --git a/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.html b/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.html index 852cc6a20d9f..50b08c79bcd2 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.html +++ b/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.html @@ -1,37 +1,40 @@ -
    +
    search - -
    -
    - {{countries().length === 0 ? 'No results found for ' + query() : ''}} -
    - - - + +
    @if (countries().length === 0) { -
    No results found
    +
    No results found
    } -
    +
    @for (country of countries(); track country) { -
    - {{country}} - check -
    +
    + {{country}} + check +
    }
    -
    \ No newline at end of file +
    diff --git a/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.ts b/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.ts index 0187abe37ffd..2b3123d5e52f 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.ts +++ b/src/components-examples/aria/autocomplete/autocomplete-highlight/autocomplete-highlight-example.ts @@ -6,9 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; import {COUNTRIES} from '../countries'; import {OverlayModule} from '@angular/cdk/overlay'; import {FormsModule} from '@angular/forms'; @@ -18,20 +30,33 @@ import {FormsModule} from '@angular/forms'; selector: 'autocomplete-highlight-example', templateUrl: 'autocomplete-highlight-example.html', styleUrl: '../autocomplete.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteHighlightExample { /** The selected value of the combobox. */ - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); + listbox = viewChild>(Listbox); - popupExpanded = signal(false); - searchString = signal(''); - selectedOption = signal([]); - navigated = signal(false); + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** A reference to the ng aria combobox input. */ + comboboxInput = viewChild(ComboboxInput); /** The query string used to filter the list of countries. */ - query = computed(() => this.searchString()); + query = computed(() => this.comboboxInput()?.value() || ''); /** The list of countries filtered by the query. */ countries = computed(() => @@ -39,37 +64,26 @@ export class AutocompleteHighlightExample { ); constructor() { + // Scrolls to the active item when the active option changes. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); - - effect(() => { - if (!this.popupExpanded()) { - this.navigated.set(false); + if (this.combobox()?.expanded()) { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); } }); } /** Clears the query and the listbox value. */ clear(): void { - this.searchString.set(''); - this.selectedOption.set([]); - } - - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - } - this.popupExpanded.set(false); - this.combobox()?.element.focus(); + this.comboboxInput()?.value.set(''); + this.listbox?.()?.values.set([]); } /** Handles keydown events on the clear button. */ onKeydown(event: KeyboardEvent): void { if (event.key === 'Enter') { this.clear(); - this.popupExpanded.set(false); + this.combobox?.()?.close(); event.stopPropagation(); } } diff --git a/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.html b/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.html index 521518a42290..6b492dc2e2db 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.html +++ b/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.html @@ -1,32 +1,37 @@ -
    +
    search - -
    - -
    - {{countries().length === 0 ? 'No results found for ' + query() : ''}} -
    - - + +
    @if (countries().length === 0) { -
    No results found
    +
    No results found
    } -
    +
    @for (country of countries(); track country) { -
    - {{country}} - check -
    +
    + {{country}} + check +
    }
    diff --git a/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.ts b/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.ts index 3384a55914b8..a1e7391bd6ea 100644 --- a/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.ts +++ b/src/components-examples/aria/autocomplete/autocomplete-manual/autocomplete-manual-example.ts @@ -6,31 +6,57 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; import {COUNTRIES} from '../countries'; import {OverlayModule} from '@angular/cdk/overlay'; import {FormsModule} from '@angular/forms'; -/** @title Combobox Autocomplete with manual filtering. */ +/** @title Autocomplete with manual filtering. */ @Component({ selector: 'autocomplete-manual-example', templateUrl: 'autocomplete-manual-example.html', styleUrl: '../autocomplete.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteManualExample { /** The selected value of the combobox. */ - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); + listbox = viewChild>(Listbox); - popupExpanded = signal(false); - searchString = signal(''); - selectedOption = signal([]); + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** A reference to the ng aria combobox input. */ + comboboxInput = viewChild(ComboboxInput); /** The query string used to filter the list of countries. */ - query = computed(() => this.searchString()); + query = computed(() => this.comboboxInput()?.value() || ''); /** The list of countries filtered by the query. */ countries = computed(() => @@ -38,31 +64,26 @@ export class AutocompleteManualExample { ); constructor() { + // Scrolls to the active item when the active option changes. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + if (this.combobox()?.expanded()) { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); + } }); } /** Clears the query and the listbox value. */ clear(): void { - this.searchString.set(''); - this.selectedOption.set([]); - } - - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - } - this.popupExpanded.set(false); - this.combobox()?.element.focus(); + this.comboboxInput()?.value.set(''); + this.listbox?.()?.values.set([]); } /** Handles keydown events on the clear button. */ onKeydown(event: KeyboardEvent): void { if (event.key === 'Enter') { this.clear(); - this.popupExpanded.set(false); + this.combobox?.()?.close(); event.stopPropagation(); } } diff --git a/src/components-examples/aria/autocomplete/autocomplete.css b/src/components-examples/aria/autocomplete/autocomplete.css index f76b595df316..2c77dbb51211 100644 --- a/src/components-examples/aria/autocomplete/autocomplete.css +++ b/src/components-examples/aria/autocomplete/autocomplete.css @@ -5,36 +5,6 @@ /* stylelint-disable-next-line material/no-prefixes -- Valid in all remotely recent browsers. */ width: fit-content; - - /* The ::placeholder selectors are intentionally separated. - The 'material/no-prefixes' stylelint rule flags the standard ::placeholder - when grouped with vendor prefixes. Each prefix is on its own rule to comply. - Vendor prefixes are included to ensure consistent placeholder styling - across different browsers. - - NOTE: These styles are primarily needed for internal testing. */ - - /* stylelint-disable material/no-prefixes -- Provides all prefixes for ::placeholder */ - input::-webkit-input-placeholder { - color: var(--mat-sys-on-surface-variant); - } - - input::-moz-placeholder /* Firefox 19+ */ { - color: var(--mat-sys-on-surface-variant); - } - - input:-ms-input-placeholder /* IE 10+ */ { - color: var(--mat-sys-on-surface-variant); - } - - input:-moz-placeholder /* Firefox 18- */ { - color: var(--mat-sys-on-surface-variant); - } - - input::placeholder { - color: var(--mat-sys-on-surface-variant); - } - } .example-search-icon, @@ -48,7 +18,7 @@ position: absolute; } -input[ngCombobox] { +[ngComboboxInput] { width: 13rem; font-size: 0.9rem; border-radius: var(--mat-sys-corner-extra-small); @@ -58,13 +28,15 @@ input[ngCombobox] { background-color: var(--mat-sys-surface); } -input[ngCombobox][aria-disabled='true'], -input[ngCombobox]:disabled { +[ngComboboxInput][aria-disabled='true'] { cursor: default; opacity: 0.5; background-color: var(--mat-sys-surface-dim); } +[ngCombobox]:has([aria-expanded='false']) .example-popup { + display: none; +} .example-clear-button { position: absolute; @@ -145,4 +117,3 @@ input[ngCombobox]:disabled { .example-check-icon { font-size: 0.9rem; } - diff --git a/src/components-examples/aria/autocomplete/index.ts b/src/components-examples/aria/autocomplete/index.ts index dcea144346df..15cb925c5e28 100644 --- a/src/components-examples/aria/autocomplete/index.ts +++ b/src/components-examples/aria/autocomplete/index.ts @@ -1,4 +1,4 @@ export {AutocompleteAutoSelectExample} from './autocomplete-auto-select/autocomplete-auto-select-example'; -export {AutocompleteDisabledExample} from './autocomplete-disabled/autocomplete-disabled-example'; -export {AutocompleteHighlightExample} from './autocomplete-highlight/autocomplete-highlight-example'; export {AutocompleteManualExample} from './autocomplete-manual/autocomplete-manual-example'; +export {AutocompleteHighlightExample} from './autocomplete-highlight/autocomplete-highlight-example'; +export {AutocompleteDisabledExample} from './autocomplete-disabled/autocomplete-disabled-example'; diff --git a/src/components-examples/aria/combobox/BUILD.bazel b/src/components-examples/aria/combobox/BUILD.bazel index 6b358e41a296..7eebc8fcaf3a 100644 --- a/src/components-examples/aria/combobox/BUILD.bazel +++ b/src/components-examples/aria/combobox/BUILD.bazel @@ -14,15 +14,9 @@ ng_project( "//:node_modules/@angular/core", "//:node_modules/@angular/forms", "//src/aria/combobox", - "//src/aria/grid", "//src/aria/listbox", "//src/aria/tree", - "//src/cdk/a11y", "//src/cdk/overlay", - "//src/material/checkbox", - "//src/material/core", - "//src/material/icon", - "//src/material/tooltip", ], ) diff --git a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html index c16dc41a8c40..bce80529017e 100644 --- a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html +++ b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.html @@ -1,33 +1,33 @@ -
    -
    +
    +
    search - +
    -
    - {{options().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (options().length === 0) { -
    No results found
    - } -
    +
    + +
    @for (option of options(); track option) { -
    - {{option}} - -
    +
    + {{option}} + +
    } -
    - -
    \ No newline at end of file +
    +
    diff --git a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts index 80d88e3d9533..5393e0af1fd3 100644 --- a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts +++ b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts @@ -6,25 +6,37 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES as states} from '../states'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; -/** @title Combobox Auto Select */ +/** @title Combobox with auto-select filtering. */ @Component({ selector: 'combobox-auto-select-example', templateUrl: 'combobox-auto-select-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../combobox-examples.css', + imports: [Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer, Listbox, Option], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxAutoSelectExample { - readonly listbox = viewChild(Listbox); + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); searchString = signal(''); - selectedOption = signal([]); options = computed(() => states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), @@ -32,15 +44,79 @@ export class ComboboxAutoSelectExample { constructor() { afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); this.listbox()?.scrollActiveItemIntoView(); }); } - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } - this.popupExpanded.set(false); + + popover.nativeElement.showPopover(); } } + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.css b/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.css deleted file mode 100644 index 942d3636b229..000000000000 --- a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.css +++ /dev/null @@ -1,106 +0,0 @@ -.example-datepicker-popup { - padding: 16px; - width: 320px; - max-height: none; - overflow: visible; - background-color: var(--mat-sys-surface); - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); - box-shadow: var(--mat-sys-level2-shadow); -} - -.example-datepicker-header { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: 12px; - border-bottom: 1px solid var(--mat-sys-outline-variant); - margin-bottom: 12px; -} - -.example-datepicker-title { - font-weight: 600; - font-size: 0.9rem; - color: var(--mat-sys-on-surface); -} - -.example-datepicker-nav-button { - background-color: transparent; - border: none; - border-radius: 50%; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - color: var(--mat-sys-on-surface); - transition: background-color 0.2s ease; -} - -.example-datepicker-nav-button:hover { - background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); -} - -.example-datepicker-grid { - width: 100%; - border-collapse: collapse; -} - -.example-datepicker-cell { - width: 40px; - height: 40px; - text-align: center; - vertical-align: middle; - padding: 0; -} - -.example-datepicker-weekday { - font-size: 0.75rem; - font-weight: 500; - color: var(--mat-sys-on-surface-variant); - padding-bottom: 8px; -} - -.example-datepicker-empty { - color: color-mix(in srgb, var(--mat-sys-on-surface) 30%, transparent); - font-size: 0.8rem; -} - -.example-datepicker-day-button { - width: 32px; - height: 32px; - border-radius: 50%; - border: none; - background-color: transparent; - cursor: pointer; - font-size: 0.85rem; - color: var(--mat-sys-on-surface); - transition: - background-color 0.2s ease, - color 0.2s ease; -} - -.example-datepicker-cell:hover .example-datepicker-day-button { - background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); -} - -.example-datepicker-cell:focus-within { - outline: 2px solid var(--mat-sys-primary); - outline-offset: -2px; -} - -.example-datepicker-day-button:focus { - outline: none; -} - -.example-datepicker-cell[aria-selected='true'] .example-datepicker-day-button { - background-color: var(--mat-sys-primary); - color: var(--mat-sys-on-primary); -} - -.example-combobox-hint { - font-size: 0.75rem; - color: var(--mat-sys-on-surface-variant); - margin-top: 4px; -} diff --git a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.html b/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.html deleted file mode 100644 index 14fb9b11f1d9..000000000000 --- a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.html +++ /dev/null @@ -1,68 +0,0 @@ -
    -
    - calendar_month - -
    - - - -
    -
    -
    - -
    {{ monthYearLabel() }}
    - -
    - - - - - @for (day of weekdays(); track day.long) { - - } - - - - - @for (week of weeks(); track $index) { - - @if ($first) { - @for (day of daysFromPrevMonth(); track $index) { - - } - } - - @for (day of week; track $index) { - - } - - @if ($last && week.length < 7) { @for (day of [].constructor(7 - week.length); track $index) { - } - } - - } - -
    - {{ day.long }} - -
    {{ day }} - - {{ $index + 1 }} -
    -
    -
    -
    -
    -
    -
    Format: MM/DD/YYYY
    \ No newline at end of file diff --git a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.ts b/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.ts deleted file mode 100644 index f0e79345c040..000000000000 --- a/src/components-examples/aria/combobox/combobox-datepicker/combobox-datepicker-example.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - inject, - Component, - WritableSignal, - signal, - Signal, - computed, - viewChild, - ElementRef, -} from '@angular/core'; -import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; -import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {A11yModule} from '@angular/cdk/a11y'; - -const DAYS_PER_WEEK = 7; - -interface CalendarCell { - displayName: string; - ariaLabel: string; - date: D; - selected: WritableSignal; -} - -/** @title Combobox with Datepicker Grid. */ -@Component({ - selector: 'combobox-datepicker-example', - templateUrl: 'combobox-datepicker-example.html', - styleUrls: ['../combobox-example.css', 'combobox-datepicker-example.css'], - imports: [ - Grid, - GridRow, - GridCell, - GridCellWidget, - Combobox, - ComboboxPopup, - ComboboxWidget, - OverlayModule, - A11yModule, - ], -}) -export class ComboboxDatepickerExample { - private readonly _dateAdapter = inject>(DateAdapter, {optional: true})!; - private readonly _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; - - readonly grid = viewChild(Grid); - readonly gridTable = viewChild>('gridTable'); - - readonly selection = signal(''); - readonly popupExpanded = signal(false); - - private readonly _firstWeekOffset: Signal = computed(() => { - const firstOfMonth = this._dateAdapter.createDate( - this._dateAdapter.getYear(this.viewMonth()), - this._dateAdapter.getMonth(this.viewMonth()), - 1, - ); - - return ( - (DAYS_PER_WEEK + - this._dateAdapter.getDayOfWeek(firstOfMonth) - - this._dateAdapter.getFirstDayOfWeek()) % - DAYS_PER_WEEK - ); - }); - - private readonly _activeDate: WritableSignal = signal(this._dateAdapter.today()); - - readonly monthYearLabel: Signal = computed(() => - this._dateAdapter - .format(this.viewMonth(), this._dateFormats.display.monthYearLabel) - .toLocaleUpperCase(), - ); - readonly prevMonthNumDays: Signal = computed(() => - this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)), - ); - readonly daysFromPrevMonth: Signal = computed(() => { - const days: number[] = []; - for (let i = this._firstWeekOffset() - 1; i >= 0; i--) { - days.push(this.prevMonthNumDays() - i); - } - return days; - }); - readonly viewMonth: WritableSignal = signal(this._dateAdapter.today()); - readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => { - const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek(); - const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow'); - const longWeekdays = this._dateAdapter.getDayOfWeekNames('long'); - - const weekdays = longWeekdays.map((long, i) => { - return {long, narrow: narrowWeekdays[i]}; - }); - return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); - }); - readonly weeks: Signal[][]> = computed(() => { - this._activeDate(); // Create dependency on active date - return this._createWeekCells(this.viewMonth()); - }); - - nextMonth(): void { - this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1)); - } - - prevMonth(): void { - this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)); - } - - readonly comboboxInput = viewChild>('comboboxInput'); - - selectDate(cell: CalendarCell): void { - const formatted = this._dateAdapter.format(cell.date, this._dateFormats.display.dateInput); - this.selection.set(formatted); - this._activeDate.set(cell.date); - this.popupExpanded.set(false); - this.comboboxInput()?.nativeElement.focus(); - } - - onInputKeydown(event: KeyboardEvent) { - if (event.key === 'Enter') { - const value = this.selection(); - const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput); - if (parsedDate && this._dateAdapter.isValid(parsedDate)) { - this._activeDate.set(parsedDate); - this.viewMonth.set(parsedDate); - this.popupExpanded.set(false); - } - } else if (event.key === 'ArrowDown' && this.popupExpanded()) { - setTimeout(() => { - const tableEl = this.gridTable()?.nativeElement; - if (tableEl) { - const tabbable = tableEl.querySelector('[tabindex="0"]') as HTMLElement; - (tabbable || tableEl).focus(); - } - }); - } - } - - /** Handles keydown events on the widget container. */ - handleWidgetKeydown(event: KeyboardEvent) { - if (event.key === 'Escape') { - this.popupExpanded.set(false); - this.comboboxInput()?.nativeElement.focus(); - event.preventDefault(); - event.stopPropagation(); - return; - } - - if (event.target === event.currentTarget) { - this.grid()?._pattern.onKeydown(event); - } - } - - private _createWeekCells(viewMonth: D): CalendarCell[][] { - const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth); - const dateNames = this._dateAdapter.getDateNames(); - const weeks: CalendarCell[][] = [[]]; - for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) { - if (cell == DAYS_PER_WEEK) { - weeks.push([]); - cell = 0; - } - const date = this._dateAdapter.createDate( - this._dateAdapter.getYear(viewMonth), - this._dateAdapter.getMonth(viewMonth), - i + 1, - ); - const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); - - weeks[weeks.length - 1].push({ - displayName: dateNames[i], - ariaLabel, - date, - selected: signal(this._dateAdapter.compareDate(date, this._activeDate()) === 0), - }); - } - return weeks; - } -} diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css new file mode 100644 index 000000000000..68d4cf47c377 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css @@ -0,0 +1,222 @@ +.example-combobox-container { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-container:has([readonly='true']) { + width: 225px; +} + +.example-combobox-input-container { + display: flex; + position: relative; + align-items: center; + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input { + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input[readonly='true'] { + cursor: pointer; + padding: 0.7rem 1rem; +} + +.example-combobox-container:focus-within .example-combobox-input { + outline: 1.5px solid var(--mat-sys-primary); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent); +} + +.example-icon { + width: 24px; + height: 24px; + font-size: 20px; + display: grid; + place-items: center; + pointer-events: none; +} + +.example-search-icon { + padding: 0 0.5rem; + position: absolute; + opacity: 0.8; +} + +.example-arrow-icon { + padding: 0 0.5rem; + position: absolute; + right: 0; + opacity: 0.8; + transition: transform 0.2s ease; +} + +.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { + transform: rotate(180deg); +} + +.example-combobox-input { + width: 100%; + border: none; + outline: none; + font-size: 1rem; + padding: 0.7rem 1rem 0.7rem 2.5rem; + background-color: var(--mat-sys-surface); +} + +.example-popover { + margin: 0; + padding: 0; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); + background-color: var(--mat-sys-surface); +} + +.example-listbox { + display: flex; + flex-direction: column; + overflow: auto; + max-height: 10rem; + padding: 0.5rem; + gap: 4px; +} + +.example-option { + cursor: pointer; + padding: 0.3rem 1rem; + border-radius: var(--mat-sys-corner-extra-small); + display: flex; + overflow: hidden; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.example-option-text { + flex: 1; +} + +.example-checkbox-blank-icon, +.example-option[aria-selected='true'] .example-checkbox-filled-icon { + display: flex; + align-items: center; +} + +.example-checkbox-filled-icon, +.example-option[aria-selected='true'] .example-checkbox-blank-icon { + display: none; +} + +.example-checkbox-blank-icon { + opacity: 0.6; +} + +.example-selected-icon { + visibility: hidden; +} + +.example-option[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-option[aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option:hover { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); +} + +.example-combobox-container:focus-within [data-active='true'] { + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); +} + +.example-tree { + padding: 10px; + overflow-x: scroll; +} + +.example-tree-item { + cursor: pointer; + list-style: none; + text-decoration: none; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.3rem 1rem; +} + +li[aria-expanded='false'] + ul[role='group'] { + display: none; +} + +ul[role='group'] { + padding-inline-start: 1rem; +} + +.example-icon { + margin: 0; + width: 24px; +} + +.example-parent-icon { + transition: transform 0.2s ease; +} + +.example-tree-item[aria-expanded='true'] .example-parent-icon { + transform: rotate(90deg); +} + +.example-selected-icon { + visibility: hidden; + margin-left: auto; +} + +.example-tree-item[aria-current] .example-selected-icon, +.example-tree-item[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-dialog { + position: absolute; + left: auto; + right: auto; + top: auto; + bottom: auto; + padding: 0; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-dialog .example-combobox-input-container { + border-radius: 0; +} + +.example-dialog .example-combobox-container, +.example-dialog .example-combobox-input-container { + border: none; +} + +.example-dialog .example-combobox-input { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.example-dialog .example-combobox-container:focus-within .example-combobox-input { + outline: none; + box-shadow: none; +} + +.example-dialog .example-combobox-input-container { + border-bottom: 1px solid var(--mat-sys-outline); +} + +.example-dialog::backdrop { + opacity: 0; +} diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html index 5c2e125c6a08..67eae7296522 100644 --- a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html @@ -1,51 +1,49 @@ -
    -
    - +
    +
    + arrow_drop_down
    - - - -
    -
    -
    -
    - search - -
    - -
    - {{options().length === 0 ? 'No results found for ' + searchString() : ''}} -
    + + +
    + +
    + search + +
    - -
    - @if (options().length === 0) { -
    No results found
    - } -
    - @for (option of options(); track option) { -
    - {{option}} - -
    - } -
    + +
    + @for (option of options(); track option) { +
    + {{option}} +
    - + }
    -
    +
    -
    + -
    \ No newline at end of file +
    diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts index 5ff1e6c7f530..fc0de32bcc93 100644 --- a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts @@ -6,32 +6,44 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxDialog, + ComboboxInput, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; import { afterRenderEffect, + ChangeDetectionStrategy, Component, computed, signal, - viewChild, untracked, - ElementRef, + viewChild, } from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; import {FormsModule} from '@angular/forms'; -import {STATES as states} from '../states'; /** @title Combobox with a dialog popup. */ @Component({ selector: 'combobox-dialog-example', templateUrl: 'combobox-dialog-example.html', - styleUrls: ['../combobox-example.css'], - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], + styleUrl: 'combobox-dialog-example.css', + imports: [ + ComboboxDialog, + Combobox, + ComboboxInput, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxDialogExample { + dialog = viewChild(ComboboxDialog); listbox = viewChild>(Listbox); - combobox = viewChild(Combobox); - searchInput = viewChild>('searchInput'); + combobox = viewChild>(Combobox); value = signal(''); searchString = signal(''); @@ -41,38 +53,93 @@ export class ComboboxDialogExample { ); selectedStates = signal([]); - popupExpanded = signal(false); constructor() { afterRenderEffect(() => { - if (this.popupExpanded()) { - untracked(() => { - setTimeout(() => { - this.searchInput()?.nativeElement.focus(); - }); - }); + if (this.dialog() && this.combobox()?.expanded()) { + untracked(() => this.listbox()?.gotoFirst()); + this.positionDialog(); } }); afterRenderEffect(() => { - if (this.popupExpanded()) { - this.listbox()?.scrollActiveItemIntoView(); + if (this.selectedStates().length > 0) { + untracked(() => this.dialog()?.close()); + this.value.set(this.selectedStates()[0]); + this.searchString.set(''); } }); - } - onCommit() { - const selected = this.selectedStates(); - if (selected.length > 0) { - this.value.set(selected[0]); - this.searchString.set(''); - this.popupExpanded.set(false); - this.combobox()?.element.focus(); - } + afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView()); } - onSearchEscape(event: Event) { - this.popupExpanded.set(false); - this.combobox()?.element.focus(); // Focus back to main trigger! + // TODO(wagnermaciel): Switch to using the CDK for positioning. + + positionDialog() { + const dialog = this.dialog()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + + const scrollY = window.scrollY; + + if (comboboxRect) { + dialog.element.style.width = `${comboboxRect.width}px`; + dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`; + dialog.element.style.left = `${comboboxRect.left - 1}px`; + } } } + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html index 4485501cd407..a791c1d1f5b4 100644 --- a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html +++ b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html @@ -1,27 +1,33 @@ -
    -
    +
    +
    search - +
    - - - -
    -
    - @for (option of options(); track option) { -
    +
    + +
    + @for (option of options(); track option) { +
    {{option}} - +
    - } -
    + }
    - -
    \ No newline at end of file +
    +
    diff --git a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts index c75ca5df7bf6..bf8e2808fd40 100644 --- a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts +++ b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts @@ -6,25 +6,46 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild, untracked} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES as states} from '../states'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {FormsModule} from '@angular/forms'; -/** @title Combobox Disabled */ +/** @title Disabled combobox example. */ @Component({ selector: 'combobox-disabled-example', templateUrl: 'combobox-disabled-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../combobox-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxDisabledExample { - readonly listbox = viewChild(Listbox); + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); searchString = signal(''); - selectedOption = signal([]); options = computed(() => states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), @@ -32,21 +53,80 @@ export class ComboboxDisabledExample { constructor() { afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - afterRenderEffect(() => { - if (this.popupExpanded()) { - untracked(() => setTimeout(() => this.listbox()?.gotoFirst())); - } + this.listbox()?.scrollActiveItemIntoView(); }); } - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - this.popupExpanded.set(false); + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } + + popover.nativeElement.showPopover(); } } + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.html b/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.html deleted file mode 100644 index b888a8afc9c0..000000000000 --- a/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.html +++ /dev/null @@ -1,52 +0,0 @@ -
    -
    - - arrow_drop_down -
    - - - - -
    -
    -
    -
    - search - -
    - -
    - {{countries().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - -
    - @if (countries().length === 0) { -
    No results found
    - } -
    - @for (country of countries(); track country) { -
    - {{country}} - -
    - } -
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.ts b/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.ts deleted file mode 100644 index eb9ef10ebdcd..000000000000 --- a/src/components-examples/aria/combobox/combobox-editable-multiselect/combobox-editable-multiselect-example.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - Component, - computed, - signal, - untracked, - viewChild, - ElementRef, -} from '@angular/core'; -import {COUNTRIES} from '../countries'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {FormsModule} from '@angular/forms'; - -/** @title Editable multiselectable combobox with a dialog layout. */ -@Component({ - selector: 'combobox-editable-multiselect-example', - templateUrl: 'combobox-editable-multiselect-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], -}) -export class ComboboxEditableMultiselectExample { - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); - readonly searchInput = viewChild>('searchInput'); - - popupExpanded = signal(false); - searchString = signal(''); - selectedOptions = signal([]); - - /** The display text for the primary readonly trigger input. */ - value = computed(() => { - const current = this.selectedOptions(); - if (current.length === 0) return ''; - if (current.length === 1) return current[0]; - return `${current[0]} + ${current.length - 1} more`; - }); - - /** The list of countries filtered by the inner search input query. */ - countries = computed(() => { - const currentQuery = this.searchString().toLowerCase(); - return COUNTRIES.filter(country => country.toLowerCase().startsWith(currentQuery)); - }); - - constructor() { - // Automatically auto-focus the inner search text field when the dialog expands - afterRenderEffect(() => { - if (this.popupExpanded()) { - untracked(() => { - setTimeout(() => { - this.searchInput()?.nativeElement.focus(); - }); - }); - } - }); - - afterRenderEffect(() => { - if (this.popupExpanded()) { - this.listbox()?.scrollActiveItemIntoView(); - } - }); - } - - /** Clears the search query and all selected options. */ - clear(): void { - this.searchString.set(''); - this.selectedOptions.set([]); - } - - /** Keeps focus inside the dialog when selection changes. */ - onCommit() { - this.searchString.set(''); - } - - /** Dismisses the dialog overlay on Escape key. */ - onSearchEscape(event: Event) { - this.popupExpanded.set(false); - this.combobox()?.element.focus(); - } - - /** Handles keydown events on the clear button. */ - onKeydown(event: KeyboardEvent): void { - if (event.key === 'Enter') { - this.clear(); - this.popupExpanded.set(false); - event.stopPropagation(); - } - } -} diff --git a/src/components-examples/aria/combobox/combobox-example.css b/src/components-examples/aria/combobox/combobox-example.css deleted file mode 100644 index 4bcf3d08001f..000000000000 --- a/src/components-examples/aria/combobox/combobox-example.css +++ /dev/null @@ -1,379 +0,0 @@ -.example-combobox-container { - position: relative; - width: 100%; - display: flex; - flex-direction: column; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); - transition: - box-shadow 0.2s ease, - border-color 0.2s ease; -} - -.example-combobox-container:has([readonly='true']:not([aria-disabled='true'])) { - width: 200px; -} - -.example-combobox-input-container { - display: flex; - position: relative; - align-items: center; - border-radius: var(--mat-sys-corner-extra-small); -} - -.example-combobox-input { - border-radius: var(--mat-sys-corner-extra-small); -} - -.example-combobox-input[readonly='true']:not([aria-disabled='true']) { - cursor: pointer; - padding: 0.7rem 1rem; -} - -.example-combobox-container:focus-within { - border-color: var(--mat-sys-primary); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent); -} - -.example-icon { - width: 24px; - height: 24px; - font-size: 24px; - color: var(--mat-sys-on-surface-variant); - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - display: grid; - place-items: center; - pointer-events: none; -} - -.example-search-icon { - padding: 0 0.5rem; - position: absolute; - opacity: 0.8; -} - -.example-arrow-icon { - padding: 0 0.5rem; - position: absolute; - right: 0; - opacity: 0.8; - transition: transform 0.2s ease; -} - -.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { - transform: rotate(180deg); -} - -.example-combobox-input { - width: 100%; - border: none; - outline: none; - font-size: 1rem; - padding: 0.7rem 1rem 0.7rem 2.5rem; - background-color: var(--mat-sys-surface); -} - -.example-popover { - margin: 0; - padding: 0; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); - background-color: var(--mat-sys-surface); -} - -.example-popup { - width: 100%; - margin-block-start: 0.25rem; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); - background-color: var(--mat-sys-surface); - max-height: 15rem; - overflow: auto; -} - -.example-listbox { - display: flex; - flex-direction: column; - overflow: auto; - max-height: 10rem; - padding: 0.5rem; - gap: 4px; - outline: none; -} - -/* --- 2. LISTBOX & OPTIONS --- */ -.example-option { - cursor: pointer; - padding: 0.3rem 1rem; - border-radius: var(--mat-sys-corner-extra-small); - display: flex; - overflow: hidden; - flex-shrink: 0; - align-items: center; - justify-content: space-between; - gap: 1rem; -} - -.example-option[aria-disabled='true'] { - cursor: not-allowed; - opacity: 0.5; - color: var(--mat-sys-on-surface-variant); - pointer-events: none; -} - -.example-option-text { - flex: 1; -} - -.example-checkbox-blank-icon, -.example-option[aria-selected='true'] .example-checkbox-filled-icon { - display: flex; - align-items: center; -} - -.example-checkbox-filled-icon, -.example-option[aria-selected='true'] .example-checkbox-blank-icon { - display: none; -} - -.example-checkbox-blank-icon { - opacity: 0.6; -} - -.example-selected-icon { - visibility: hidden; -} - -.example-option[aria-selected='true'] .example-selected-icon { - visibility: visible; -} - -.example-option[aria-selected='true'] { - color: var(--mat-sys-primary); - background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); -} - -.example-option:hover { - background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); -} - -.example-combobox-container:not(.example-no-active-outline):focus-within - [data-active='true']:not(.example-no-active-outline) { - outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); - outline-offset: -2px; -} - -.example-dialog .example-combobox-input-container { - border-bottom: 1px solid var(--mat-sys-outline); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - - -.example-tree { - padding: 10px; - overflow-x: scroll; - width: 100%; - box-sizing: border-box; -} - -.example-tree-item { - cursor: pointer; - list-style: none; - text-decoration: none; - display: flex; - align-items: center; - gap: 1rem; - padding: 0.3rem 1rem; -} - -li[aria-expanded='false'] + ul[role='group'] { - display: none; -} - -ul[role='group'] { - padding-inline-start: 1rem; -} - -.example-icon { - margin: 0; - width: 24px; -} - -.example-parent-icon { - transition: transform 0.2s ease; -} - -.example-tree-item[aria-expanded='true'] .example-parent-icon { - transform: rotate(90deg); -} - -.example-selected-icon { - visibility: hidden; - margin-left: auto; -} - -.example-tree-item[aria-current] .example-selected-icon, -.example-tree-item[aria-selected='true'] .example-selected-icon { - visibility: visible; -} - -.example-combobox-container:has(.example-combobox-input[aria-disabled='true']) { - opacity: 0.4; - cursor: default; -} - -.example-grid-row { - display: flex; - align-items: center; - border-radius: var(--mat-sys-corner-extra-small); - transition: background-color 0.2s ease; -} - -.example-grid-row.example-selectable { - cursor: pointer; -} - -.example-grid-row.example-selectable:hover { - background-color: color-mix(in srgb, var(--mat-sys-on-surface) 5%, transparent); -} - -.example-grid-row.example-selectable:active { - background-color: color-mix(in srgb, var(--mat-sys-primary) 20%, transparent); -} - -.example-grid-row[data-active='true'] { - background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); - outline: 2px solid var(--mat-sys-primary); -} - -.example-grid-row[aria-selected='true'] { - color: var(--mat-sys-primary); - background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); -} - -.example-grid-header-row { - display: flex; - gap: 12px; - padding: 8px; - background-color: color-mix(in srgb, var(--mat-sys-on-surface) 5%, transparent); - border-bottom: 1px solid var(--mat-sys-outline); - font-weight: 600; - font-size: 0.85rem; -} - -.example-cell { - flex: 1; - display: flex; - align-items: center; - min-height: 40px; -} - -.example-cell-header { - flex: 1; - display: flex; - align-items: center; -} - -.example-cell-label { - border-radius: var(--mat-sys-corner-extra-small); - flex: 2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 12px; -} - -.example-cell-checkbox, -.example-cell-button { - flex: 0 0 auto; - justify-content: center; -} - -.example-cell-input { - flex: 1; -} - -.example-button { - cursor: pointer; - opacity: 0.6; - padding: 0; - margin: 0; - height: 100%; - width: 40px; - border: none; - background: transparent; - display: grid; - place-items: center; - color: var(--mat-sys-on-surface); - transition: opacity 0.2s, color 0.2s, transform 0.2s; -} - -.example-button mat-icon { - font-size: 20px; - width: 20px; - height: 20px; -} - -.example-button:focus, -.example-button:hover, -.example-button[data-active='true'] { - opacity: 1; - color: var(--mat-sys-primary); - outline: none; -} - -.example-button:active { - transform: scale(0.95); -} - -.example-button[aria-pressed='true'], -.example-button[aria-checked='true'] { - color: var(--mat-sys-primary); -} - -.example-button[aria-disabled='true'] { - cursor: default; - opacity: 0.45; -} - -.example-label-button { - background: transparent; - border: none; - padding: 0; - margin: 0; - font: inherit; - color: inherit; - cursor: pointer; - text-align: left; - width: 100%; - height: 100%; - outline: none; -} - -.example-popup .example-button[data-active='true'] { - color: inherit; -} - -.example-grid-row[aria-selected='true'] .example-selected-icon { - visibility: visible; -} - -.example-no-results { - padding: 1rem; -} - -.example-empty { - position: absolute; - visibility: hidden; - pointer-events: none; - height: 0; - width: 0; - overflow: hidden; -} - -.example-popup-no-margin { - margin-block-start: 0; -} diff --git a/src/components-examples/aria/combobox/combobox-examples.css b/src/components-examples/aria/combobox/combobox-examples.css new file mode 100644 index 000000000000..266f47980eb6 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-examples.css @@ -0,0 +1,189 @@ +.example-combobox-container { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-container:has([readonly='true']:not([aria-disabled='true'])) { + width: 200px; +} + +.example-combobox-input-container { + display: flex; + position: relative; + align-items: center; + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input { + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input[readonly='true']:not([aria-disabled='true']) { + cursor: pointer; + padding: 0.7rem 1rem; +} + +.example-combobox-container:focus-within .example-combobox-input { + outline: 1.5px solid var(--mat-sys-primary); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent); +} + +.example-icon { + width: 24px; + height: 24px; + font-size: 20px; + display: grid; + place-items: center; + pointer-events: none; +} + +.example-search-icon { + padding: 0 0.5rem; + position: absolute; + opacity: 0.8; +} + +.example-arrow-icon { + padding: 0 0.5rem; + position: absolute; + right: 0; + opacity: 0.8; + transition: transform 0.2s ease; +} + +.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { + transform: rotate(180deg); +} + +.example-combobox-input { + width: 100%; + border: none; + outline: none; + font-size: 1rem; + padding: 0.7rem 1rem 0.7rem 2.5rem; + background-color: var(--mat-sys-surface); +} + +.example-popover { + margin: 0; + padding: 0; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); + background-color: var(--mat-sys-surface); +} + +.example-listbox { + display: flex; + flex-direction: column; + overflow: auto; + max-height: 10rem; + padding: 0.5rem; + gap: 4px; +} + +.example-option { + cursor: pointer; + padding: 0.3rem 1rem; + border-radius: var(--mat-sys-corner-extra-small); + display: flex; + overflow: hidden; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.example-option-text { + flex: 1; +} + +.example-checkbox-blank-icon, +.example-option[aria-selected='true'] .example-checkbox-filled-icon { + display: flex; + align-items: center; +} + +.example-checkbox-filled-icon, +.example-option[aria-selected='true'] .example-checkbox-blank-icon { + display: none; +} + +.example-checkbox-blank-icon { + opacity: 0.6; +} + +.example-selected-icon { + visibility: hidden; +} + +.example-option[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-option[aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option:hover { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); +} + +.example-combobox-container:focus-within [data-active='true'] { + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); +} + +.example-tree { + padding: 10px; + overflow-x: scroll; +} + +.example-tree-item { + cursor: pointer; + list-style: none; + text-decoration: none; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.3rem 1rem; +} + +li[aria-expanded='false'] + ul[role='group'] { + display: none; +} + +ul[role='group'] { + padding-inline-start: 1rem; +} + +.example-icon { + margin: 0; + width: 24px; +} + +.example-parent-icon { + transition: transform 0.2s ease; +} + +.example-tree-item[aria-expanded='true'] .example-parent-icon { + transform: rotate(90deg); +} + +.example-selected-icon { + visibility: hidden; + margin-left: auto; +} + +.example-tree-item[aria-current] .example-selected-icon, +.example-tree-item[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-combobox-container:has([aria-disabled='true']) { + opacity: 0.4; + cursor: default; +} diff --git a/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.html b/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.html deleted file mode 100644 index f6191d9cead4..000000000000 --- a/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.html +++ /dev/null @@ -1,43 +0,0 @@ -
    -
    - search - -
    - -
    - {{filteredItems().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (filteredItems().length === 0) { -
    No results found
    - } -
    - @for (item of filteredItems(); track item.label; let i = $index) { -
    -
    - - check -
    -
    - -
    -
    - } -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.ts b/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.ts deleted file mode 100644 index dd513aefcbd8..000000000000 --- a/src/components-examples/aria/combobox/combobox-grid/combobox-grid-example.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; -import {MatIconModule} from '@angular/material/icon'; - -/** @title */ -@Component({ - selector: 'combobox-grid-example', - templateUrl: 'combobox-grid-example.html', - styleUrl: '../combobox-example.css', - imports: [ - Combobox, - ComboboxPopup, - ComboboxWidget, - OverlayModule, - Grid, - GridRow, - GridCell, - GridCellWidget, - MatIconModule, - ], -}) -export class ComboboxGridExample { - readonly grid = viewChild(Grid); - - popupExpanded = signal(true); - searchString = signal(''); - readonly selectedItem = signal<{label: string} | null>(null); - - constructor() { - afterRenderEffect(() => { - this.grid()?.scrollActiveCellIntoView({block: 'nearest'}); - }); - } - - readonly items = signal([ - {label: 'Antelope'}, - {label: 'Bird'}, - {label: 'Cat'}, - {label: 'Dog'}, - {label: 'Elephant'}, - {label: 'Fox'}, - {label: 'Giraffe'}, - {label: 'Hamster'}, - {label: 'Hippo'}, - {label: 'Iguana'}, - {label: 'Jaguar'}, - {label: 'Koala'}, - {label: 'Lion'}, - {label: 'Monkey'}, - {label: 'Nightingale'}, - {label: 'Owl'}, - {label: 'Panda'}, - {label: 'Quokka'}, - {label: 'Rabbit'}, - {label: 'Snake'}, - {label: 'Tiger'}, - {label: 'Umbrella Bird'}, - {label: 'Vulture'}, - {label: 'Whale'}, - {label: 'X-ray Tetra'}, - {label: 'Yak'}, - {label: 'Zebra'}, - ]); - - readonly filteredItems = computed(() => { - const search = this.searchString().toLowerCase(); - return [...this.items()].filter(item => item.label.toLowerCase().includes(search)); - }); - - removeItem(itemToRemove: {label: string}) { - this.items.update(items => items.filter(item => item !== itemToRemove)); - } - - selectItem(item: {label: string}) { - this.selectedItem.set(item); - this.searchString.set(item.label); - this.popupExpanded.set(false); - } - - onBlur() { - const selectedItem = this.selectedItem(); - if ( - this.searchString() === '' || - (selectedItem !== null && this.searchString() === selectedItem.label) - ) { - this.selectedItem.set(null); - } - } -} diff --git a/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.html b/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.html deleted file mode 100644 index 52f6a09f7ca7..000000000000 --- a/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.html +++ /dev/null @@ -1,27 +0,0 @@ -
    -
    - search - -
    - - - - -
    -
    - @for (option of options(); track option) { -
    - {{option}} - -
    - } -
    -
    -
    -
    -
    diff --git a/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.ts b/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.ts deleted file mode 100644 index 187030f4db22..000000000000 --- a/src/components-examples/aria/combobox/combobox-hard-disabled/combobox-hard-disabled-example.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild, untracked} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES as states} from '../states'; - -/** @title Combobox Hard Disabled */ -@Component({ - selector: 'combobox-hard-disabled-example', - templateUrl: 'combobox-hard-disabled-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], -}) -export class ComboboxHardDisabledExample { - readonly listbox = viewChild(Listbox); - - popupExpanded = signal(false); - searchString = signal(''); - selectedOption = signal([]); - - options = computed(() => - states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), - ); - - constructor() { - afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); - - afterRenderEffect(() => { - if (this.popupExpanded()) { - untracked(() => setTimeout(() => this.listbox()?.gotoFirst())); - } - }); - } - - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - this.popupExpanded.set(false); - } - } -} diff --git a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.html b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.html index c9e135c8d654..1d64419534d7 100644 --- a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.html +++ b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.html @@ -1,36 +1,33 @@ -
    -
    +
    +
    search - +
    -
    - {{options().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (options().length === 0) { -
    No results found
    - } -
    - @for (option of options(); track option.name) { -
    - {{option.name}} - +
    + +
    + @for (option of options(); track option) { +
    + {{option}} +
    - } -
    + }
    - -
    \ No newline at end of file +
    +
    diff --git a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts index 64a46e2174fe..86f8ef5e07cf 100644 --- a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts +++ b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts @@ -6,64 +6,127 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES} from '../states'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {FormsModule} from '@angular/forms'; -/** @title Combobox Highlight */ +/** @title Combobox with highlight filtering. */ @Component({ selector: 'combobox-highlight-example', templateUrl: 'combobox-highlight-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../combobox-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxHighlightExample { - readonly listbox = viewChild(Listbox); + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); searchString = signal(''); - selectedOption = signal([]); - navigated = signal(false); options = computed(() => - states.filter(state => state.name.toLowerCase().startsWith(this.searchString().toLowerCase())), + states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), ); constructor() { afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - effect(() => { - if (!this.popupExpanded()) { - this.navigated.set(false); - } + this.listbox()?.scrollActiveItemIntoView(); }); } - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - const matchedState = states.find(s => s.name === selectedOption[0]); - if (matchedState?.disabled) { - return; - } - this.searchString.set(selectedOption[0]); - } else { - this.searchString.set(''); + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } - this.popupExpanded.set(false); - } -} -interface StateOption { - name: string; - disabled: boolean; + popover.nativeElement.showPopover(); + } } -const states: StateOption[] = STATES.map((name: string, index: number) => ({ - name, - disabled: index === 1 || index === 3 || index === 4, -})); +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.html b/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.html deleted file mode 100644 index f1e0411d3529..000000000000 --- a/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.html +++ /dev/null @@ -1,33 +0,0 @@ -
    -
    - search - -
    - -
    - {{options().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (options().length === 0) { -
    No results found
    - } -
    - @for (option of options(); track option) { -
    - {{option}} - -
    - } -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.ts b/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.ts deleted file mode 100644 index 702bd1597b12..000000000000 --- a/src/components-examples/aria/combobox/combobox-listbox/combobox-listbox-example.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES as states} from '../states'; - -/** @title */ -@Component({ - selector: 'combobox-listbox-example', - templateUrl: 'combobox-listbox-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], -}) -export class ComboboxListboxExample { - readonly listbox = viewChild(Listbox); - - popupExpanded = signal(false); - searchString = signal(''); - selectedOption = signal([]); - - options = computed(() => - states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), - ); - - constructor() { - afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); - } - - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.searchString.set(selectedOption[0]); - } - this.popupExpanded.set(false); - } -} diff --git a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.html b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.html new file mode 100644 index 000000000000..e80a360531a5 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.html @@ -0,0 +1,33 @@ +
    +
    + search + +
    + +
    + +
    + @for (option of options(); track option) { +
    + {{option}} + +
    + } +
    +
    +
    +
    diff --git a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts new file mode 100644 index 000000000000..4075b3cdae62 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts @@ -0,0 +1,132 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +/** @title Combobox with manual selection. */ +@Component({ + selector: 'combobox-manual-example', + templateUrl: 'combobox-manual-example.html', + styleUrl: '../combobox-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxManualExample { + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); + + searchString = signal(''); + + options = computed(() => + states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + + this.listbox()?.scrollActiveItemIntoView(); + }); + } + + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; + } + + popover.nativeElement.showPopover(); + } +} + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.html b/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.html deleted file mode 100644 index b888a8afc9c0..000000000000 --- a/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.html +++ /dev/null @@ -1,52 +0,0 @@ -
    -
    - - arrow_drop_down -
    - - - - -
    -
    -
    -
    - search - -
    - -
    - {{countries().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - -
    - @if (countries().length === 0) { -
    No results found
    - } -
    - @for (country of countries(); track country) { -
    - {{country}} - -
    - } -
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.ts b/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.ts deleted file mode 100644 index 386cc1802a81..000000000000 --- a/src/components-examples/aria/combobox/combobox-multiselect-dialog/combobox-multiselect-dialog-example.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - Component, - computed, - signal, - untracked, - viewChild, - ElementRef, -} from '@angular/core'; -import {COUNTRIES} from '../countries'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {FormsModule} from '@angular/forms'; - -/** @title Multiselectable combobox with a dialog layout. */ -@Component({ - selector: 'combobox-multiselect-dialog-example', - templateUrl: 'combobox-multiselect-dialog-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule], -}) -export class ComboboxMultiselectDialogExample { - readonly listbox = viewChild(Listbox); - readonly combobox = viewChild(Combobox); - readonly searchInput = viewChild>('searchInput'); - - popupExpanded = signal(false); - searchString = signal(''); - selectedOptions = signal([]); - - /** The display text for the primary readonly trigger input. */ - value = computed(() => { - const current = this.selectedOptions(); - if (current.length === 0) return ''; - if (current.length === 1) return current[0]; - return `${current[0]} + ${current.length - 1} more`; - }); - - /** The list of countries filtered by the inner search input query. */ - countries = computed(() => { - const currentQuery = this.searchString().toLowerCase(); - return COUNTRIES.filter(country => country.toLowerCase().startsWith(currentQuery)); - }); - - constructor() { - // Automatically focus the inner search text field when the dialog expands - afterRenderEffect(() => { - if (this.popupExpanded()) { - untracked(() => { - setTimeout(() => { - this.searchInput()?.nativeElement.focus(); - }); - }); - } - }); - - afterRenderEffect(() => { - if (this.popupExpanded()) { - this.listbox()?.scrollActiveItemIntoView(); - } - }); - } - - /** Clears the search query and all selected options. */ - clear(): void { - this.searchString.set(''); - this.selectedOptions.set([]); - } - - /** Keeps selection state clean inside the open dialog. */ - onCommit() { - this.searchString.set(''); - } - - /** Dismisses the dialog overlay on Escape key. */ - onSearchEscape(event: Event) { - this.popupExpanded.set(false); - this.combobox()?.element.focus(); - } - - /** Handles keydown events on the clear button. */ - onKeydown(event: KeyboardEvent): void { - if (event.key === 'Enter') { - this.clear(); - this.popupExpanded.set(false); - event.stopPropagation(); - } - } -} diff --git a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html index 4d3e970e0925..0bffcd456559 100644 --- a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html +++ b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html @@ -1,27 +1,26 @@ -
    - {{value()}} - arrow_drop_down -
    +
    +
    + {{ displayValue() }} + + arrow_drop_down +
    - - -
    -
    - @for (option of options(); track option.value) { -
    - @if (option.icon) { - {{option.icon}} - } - {{option.value}} - @if (selectedValues().includes(option.value)) { - + + +
    +
    + @for (label of labels; track label.value) { +
    + {{label.icon}} + {{label.value}} + check +
    }
    - }
    -
    + - \ No newline at end of file +
    diff --git a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts index b7d65a65144f..ef1b9a3a4e3d 100644 --- a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts +++ b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts @@ -6,23 +6,54 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; import {OverlayModule} from '@angular/cdk/overlay'; -/** @title Disabled combobox. */ +/** @title Disabled readonly combobox. */ @Component({ selector: 'combobox-readonly-disabled-example', templateUrl: 'combobox-readonly-disabled-example.html', - styleUrl: '../combobox-select/combobox-select-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxReadonlyDisabledExample { - readonly listbox = viewChild(Listbox); + /** The string that is displayed in the combobox. */ + displayValue = signal(''); - readonly options = signal([ - {value: 'Select a label', icon: ''}, + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ {value: 'Important', icon: 'label'}, {value: 'Starred', icon: 'star'}, {value: 'Work', icon: 'work'}, @@ -31,22 +62,33 @@ export class ComboboxReadonlyDisabledExample { {value: 'Later', icon: 'schedule'}, {value: 'Read', icon: 'menu_book'}, {value: 'Travel', icon: 'flight'}, - ]); - readonly value = signal('Select a label'); - readonly selectedValues = signal(['Select a label']); - readonly popupExpanded = signal(false); + ]; constructor() { + // Updates the display value when the listbox values change. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + const values = this.listbox()?.values() || []; + if (values.length === 0) { + this.displayValue.set('Select a label'); + } else if (values.length === 1) { + this.displayValue.set(values[0]); + } else { + this.displayValue.set(`${values[0]} + ${values.length - 1} more`); + } }); - } - onCommit() { - const values = this.selectedValues(); - if (values.length) { - this.value.set(values[0]); - this.popupExpanded.set(false); - } + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); + }); + + // Resets the listbox scroll position when the combobox is closed. + afterRenderEffect(() => { + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } + }); } } diff --git a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html index 7860373e3025..07a5bc78a105 100644 --- a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html +++ b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html @@ -1,26 +1,26 @@ -
    - {{value()}} - arrow_drop_down -
    +
    +
    + {{ displayValue() }} + + arrow_drop_down +
    - - -
    -
    - @for (option of options(); track option.value) { -
    - @if (option.icon) { - {{option.icon}} - } - {{option.value}} - @if (selectedValues().includes(option.value)) { - + + +
    +
    + @for (label of labels; track label.value) { +
    + {{label.icon}} + {{label.value}} + check +
    }
    - }
    -
    + - \ No newline at end of file +
    diff --git a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts index 417fe8365718..1e6a6eb4c6d1 100644 --- a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts +++ b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts @@ -6,22 +6,54 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; import {OverlayModule} from '@angular/cdk/overlay'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; /** @title Readonly multiselectable combobox. */ @Component({ selector: 'combobox-readonly-multiselect-example', templateUrl: 'combobox-readonly-multiselect-example.html', - styleUrl: '../combobox-select/combobox-select-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxReadonlyMultiselectExample { - readonly listbox = viewChild(Listbox); + /** The string that is displayed in the combobox. */ + displayValue = signal(''); - readonly options = signal([ + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ {value: 'Important', icon: 'label'}, {value: 'Starred', icon: 'star'}, {value: 'Work', icon: 'work'}, @@ -30,23 +62,33 @@ export class ComboboxReadonlyMultiselectExample { {value: 'Later', icon: 'schedule'}, {value: 'Read', icon: 'menu_book'}, {value: 'Travel', icon: 'flight'}, - ]); - readonly selectedValues = signal([]); - readonly value = computed(() => { - const values = this.selectedValues(); - if (values.length === 0) { - return 'Select a label'; - } else if (values.length === 1) { - return values[0]; - } else { - return `${values[0]} + ${values.length - 1} more`; - } - }); - readonly popupExpanded = signal(false); + ]; constructor() { + // Updates the display value when the listbox values change. + afterRenderEffect(() => { + const values = this.listbox()?.values() || []; + if (values.length === 0) { + this.displayValue.set('Select a label'); + } else if (values.length === 1) { + this.displayValue.set(values[0]); + } else { + this.displayValue.set(`${values[0]} + ${values.length - 1} more`); + } + }); + + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); + }); + + // Resets the listbox scroll position when the combobox is closed. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } }); } } diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html index e60fc034b7f1..eb10faa22c2b 100644 --- a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html +++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html @@ -1,23 +1,23 @@ -
    -
    - search - +
    +
    + {{ displayValue() }} + + arrow_drop_down
    - - -
    -
    - @for (option of options(); track option) { -
    - {{option}} - -
    + + +
    +
    + @for (label of labels; track label.value) { +
    + {{label.icon}} + {{label.value}} + check +
    }
    diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts index 2325a2ab69c2..264d221920b4 100644 --- a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts +++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts @@ -6,37 +6,84 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; import {OverlayModule} from '@angular/cdk/overlay'; -import {STATES as states} from '../states'; -/** @title Combobox Readonly */ +/** @title Readonly combobox. */ @Component({ selector: 'combobox-readonly-example', templateUrl: 'combobox-readonly-example.html', - styleUrl: '../combobox-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxReadonlyExample { - readonly listbox = viewChild(Listbox); + /** The string that is displayed in the combobox. */ + displayValue = signal(''); - popupExpanded = signal(false); - searchString = signal('California'); - selectedOption = signal(['California']); + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); - options = computed(() => - states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), - ); + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ + {value: 'Important', icon: 'label'}, + {value: 'Starred', icon: 'star'}, + {value: 'Work', icon: 'work'}, + {value: 'Personal', icon: 'person'}, + {value: 'To Do', icon: 'checklist'}, + {value: 'Later', icon: 'schedule'}, + {value: 'Read', icon: 'menu_book'}, + {value: 'Travel', icon: 'flight'}, + ]; constructor() { + // Updates the display value when the listbox values change. afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); + const values = this.listbox()?.values() || []; + const displayValue = values.length ? values[0] : 'Select a label'; + this.displayValue.set(displayValue); + }); + + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); }); - } - onCommit() { - // Readonly combobox suppresses commits + // Resets the listbox scroll position when the combobox is closed. + afterRenderEffect(() => { + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } + }); } } diff --git a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.html b/src/components-examples/aria/combobox/combobox-select/combobox-select-example.html deleted file mode 100644 index 25b73e7be298..000000000000 --- a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.html +++ /dev/null @@ -1,27 +0,0 @@ -
    - {{value()}} - arrow_drop_down -
    - - - -
    -
    - @for (option of options(); track option.value) { -
    - @if (option.icon) { - {{option.icon}} - } - {{option.value}} - @if (selectedValues().includes(option.value)) { - - } -
    - } -
    -
    -
    -
    \ No newline at end of file diff --git a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.ts b/src/components-examples/aria/combobox/combobox-select/combobox-select-example.ts deleted file mode 100644 index f35319aef26f..000000000000 --- a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Component, signal, afterRenderEffect, viewChild} from '@angular/core'; -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'combobox-select-example', - templateUrl: 'combobox-select-example.html', - styleUrl: 'combobox-select-example.css', - imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], -}) -export class ComboboxSelectExample { - readonly listbox = viewChild(Listbox); - - readonly options = signal([ - {value: 'Select a label', icon: ''}, - {value: 'Important', icon: 'label'}, - {value: 'Starred', icon: 'star'}, - {value: 'Work', icon: 'work'}, - {value: 'Personal', icon: 'person'}, - {value: 'To Do', icon: 'checklist'}, - {value: 'Later', icon: 'schedule'}, - {value: 'Read', icon: 'menu_book'}, - {value: 'Travel', icon: 'flight'}, - ]); - readonly value = signal('Select a label'); - readonly selectedValues = signal(['Select a label']); - readonly popupExpanded = signal(false); - - constructor() { - afterRenderEffect(() => { - this.listbox()?.scrollActiveItemIntoView(); - }); - } - - onCommit() { - const values = this.selectedValues(); - if (values.length) { - this.value.set(values[0]); - this.popupExpanded.set(false); - } - } -} diff --git a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html index c711666e774f..fe7a5fa8af62 100644 --- a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html +++ b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html @@ -1,48 +1,63 @@ -
    -
    +
    +
    search - +
    -
    - {{filteredGroups().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (filteredGroups().length === 0) { -
    No results found
    - } -
      - -
    -
    +
    + +
      + +
    - +
    @for (node of nodes; track node.name) { -
  • - - {{ node.name }} - -
  • - @if (node.children) { -
      - - - -
    - } +
  • + + {{ node.name }} + +
  • + + @if (node.children) { +
      + + + +
    + } } -
    \ No newline at end of file + diff --git a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts index d744ec0027c0..d35c710b8dfe 100644 --- a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts +++ b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts @@ -6,142 +6,99 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; -import {Component, afterRenderEffect, computed, signal, viewChild, untracked} from '@angular/core'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {TREE_NODES, TreeNode} from '../data'; import {NgTemplateOutlet} from '@angular/common'; -import {OverlayModule} from '@angular/cdk/overlay'; - -interface SeasonNode { - name: string; - children?: SeasonNode[]; - expanded?: boolean; -} /** @title Combobox with tree popup and auto-select filtering. */ @Component({ selector: 'combobox-tree-auto-select-example', templateUrl: 'combobox-tree-auto-select-example.html', - styleUrl: '../combobox-example.css', + styleUrl: '../combobox-examples.css', imports: [ Combobox, + ComboboxInput, ComboboxPopup, - ComboboxWidget, - NgTemplateOutlet, + ComboboxPopupContainer, Tree, TreeItem, TreeItemGroup, - OverlayModule, + NgTemplateOutlet, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxTreeAutoSelectExample { - readonly tree = viewChild(Tree); + popover = viewChild('popover'); + tree = viewChild>(Tree); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); searchString = signal(''); - selectedValues = signal([]); - readonly dataSource = signal(SEASON_DATA); + nodes = computed(() => this.filterTreeNodes(TREE_NODES)); - constructor() { - afterRenderEffect(() => this._focusAndSelectFirstMatch()); + firstMatch = computed(() => { + const flatNodes = this.flattenTreeNodes(this.nodes()); + const node = flatNodes.find(n => this.isMatch(n)); + return node?.name; + }); - afterRenderEffect(() => { - const active = this.tree()?._pattern.activeItem(); - if (active) { - untracked(() => { - active.element()?.scrollIntoView({block: 'nearest'}); - }); - } + flattenTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.flatMap(node => { + return node.children ? [node, ...this.flattenTreeNodes(node.children)] : [node]; }); } - // Selects the first matching child within the tree filters. - private _focusAndSelectFirstMatch() { - this.filteredGroups(); - - const option = this.firstMatchingOption(); - const treeInstance = this.tree(); - if (option && treeInstance) { - untracked(() => { - const matchedItem = treeInstance._pattern.items().find(item => item.value() === option); - if (matchedItem) { - treeInstance._pattern.treeBehavior.goto(matchedItem, {selectOne: true}); - } - }); - } - } - - filteredData = computed(() => { - const search = this.searchString().trim().toLowerCase(); - const data = this.dataSource(); - - if (!search) { - return {groups: data, firstMatch: undefined}; - } - - let firstMatch: string | undefined = undefined; - - const filterNode = (node: SeasonNode): SeasonNode | null => { - // Find the first leaf node that starts with the search string - if (!firstMatch && !node.children && node.name.toLowerCase().startsWith(search)) { - firstMatch = node.name; - } - - const matches = node.name.toLowerCase().includes(search); - const children = node.children - ?.map(child => filterNode(child)) - .filter((child): child is SeasonNode => child !== null); - - if (matches || (children && children.length > 0)) { - return { - ...node, - children, - expanded: children && children.length > 0 ? true : node.expanded, - }; + filterTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.reduce((acc, node) => { + const children = node.children ? this.filterTreeNodes(node.children) : undefined; + if (this.isMatch(node) || (children && children.length > 0)) { + acc.push({...node, children}); } + return acc; + }, [] as TreeNode[]); + } - return null; - }; - - const groups = data - .map(node => filterNode(node)) - .filter((node): node is SeasonNode => node !== null); - return {groups, firstMatch}; - }); + isMatch(node: TreeNode) { + return node.name.toLowerCase().includes(this.searchString().toLowerCase()); + } - filteredGroups = computed(() => this.filteredData().groups); - firstMatchingOption = computed(() => this.filteredData().firstMatch); + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); + }); + } - onCommit() { - const treeInstance = this.tree(); - if (!treeInstance) return; + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; - const activeItem = treeInstance._pattern.activeItem(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; - if (activeItem) { - if (activeItem.selectable()) { - // Selectable child: commit value and close popup. - const selected = this.selectedValues(); - if (selected.length > 0) { - this.searchString.set(selected[0]); - this.popupExpanded.set(false); - } - } else { - // Non-selectable parent: expand and focus its first child. - const children = activeItem.children(); - if (children.length > 0) { - const firstChild = children[0]; - treeInstance._pattern.treeBehavior.goto(firstChild); - } - } + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } + + popover.nativeElement.showPopover(); } } - -const SEASON_DATA: SeasonNode[] = [ - {name: 'Winter', children: [{name: 'December'}, {name: 'January'}, {name: 'February'}]}, - {name: 'Spring', children: [{name: 'March'}, {name: 'April'}, {name: 'May'}]}, - {name: 'Summer', children: [{name: 'June'}, {name: 'July'}, {name: 'August'}]}, - {name: 'Fall', children: [{name: 'September'}, {name: 'October'}, {name: 'November'}]}, -]; diff --git a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html index d646d6be833e..14f4f5999c41 100644 --- a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html +++ b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html @@ -1,50 +1,63 @@ -
    -
    +
    +
    search - +
    -
    - {{filteredGroups().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (filteredGroups().length === 0) { -
    No results found
    - } -
      - -
    -
    +
    + +
      + +
    - +
    @for (node of nodes; track node.name) { -
  • - - {{ node.name }} - -
  • - @if (node.children) { -
      - - - -
    - } +
  • + + {{ node.name }} + +
  • + + @if (node.children) { +
      + + + +
    + } } -
    \ No newline at end of file + diff --git a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts index 13215abbe751..aefd4830422e 100644 --- a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts +++ b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts @@ -6,157 +6,99 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; import { - Component, afterRenderEffect, + ChangeDetectionStrategy, + Component, computed, - effect, + ElementRef, signal, viewChild, - untracked, } from '@angular/core'; +import {TREE_NODES, TreeNode} from '../data'; import {NgTemplateOutlet} from '@angular/common'; -import {OverlayModule} from '@angular/cdk/overlay'; - -interface SeasonNode { - name: string; - children?: SeasonNode[]; - expanded?: boolean; -} /** @title Combobox with tree popup and highlight filtering. */ @Component({ selector: 'combobox-tree-highlight-example', templateUrl: 'combobox-tree-highlight-example.html', - styleUrl: '../combobox-example.css', + styleUrl: '../combobox-examples.css', imports: [ Combobox, + ComboboxInput, ComboboxPopup, - ComboboxWidget, - NgTemplateOutlet, + ComboboxPopupContainer, Tree, TreeItem, TreeItemGroup, - OverlayModule, + NgTemplateOutlet, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxTreeHighlightExample { - readonly tree = viewChild(Tree); + popover = viewChild('popover'); + tree = viewChild>(Tree); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); searchString = signal(''); - selectedValues = signal([]); - navigated = signal(false); - readonly dataSource = signal(SEASON_DATA); + nodes = computed(() => this.filterTreeNodes(TREE_NODES)); - constructor() { - afterRenderEffect(() => this._focusAndSelectFirstMatch()); + firstMatch = computed(() => { + const flatNodes = this.flattenTreeNodes(this.nodes()); + const node = flatNodes.find(n => this.isMatch(n)); + return node?.name; + }); - afterRenderEffect(() => { - const active = this.tree()?._pattern.activeItem(); - if (active) { - untracked(() => { - active.element()?.scrollIntoView({block: 'nearest'}); - }); - } + flattenTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.flatMap(node => { + return node.children ? [node, ...this.flattenTreeNodes(node.children)] : [node]; }); + } - effect(() => { - if (!this.popupExpanded()) { - this.navigated.set(false); + filterTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.reduce((acc, node) => { + const children = node.children ? this.filterTreeNodes(node.children) : undefined; + if (this.isMatch(node) || (children && children.length > 0)) { + acc.push({...node, children}); } - }); + return acc; + }, [] as TreeNode[]); } - // Selects the first matching child within the tree filters. - private _focusAndSelectFirstMatch() { - this.filteredGroups(); - - const option = this.firstMatchingOption(); - const treeInstance = this.tree(); - if (option && treeInstance) { - untracked(() => { - const matchedItem = treeInstance._pattern.items().find(item => item.value() === option); - if (matchedItem) { - treeInstance._pattern.treeBehavior.goto(matchedItem, {selectOne: true}); - } - }); - } + isMatch(node: TreeNode) { + return node.name.toLowerCase().includes(this.searchString().toLowerCase()); } - filteredData = computed(() => { - const search = this.searchString().trim().toLowerCase(); - const data = this.dataSource(); - - if (!search) { - return {groups: data, firstMatch: undefined}; - } - - let firstMatch: string | undefined = undefined; - - const filterNode = (node: SeasonNode): SeasonNode | null => { - // Find the first leaf node that starts with the search string - if (!firstMatch && !node.children && node.name.toLowerCase().startsWith(search)) { - firstMatch = node.name; - } - - const matches = node.name.toLowerCase().includes(search); - const children = node.children - ?.map(child => filterNode(child)) - .filter((child): child is SeasonNode => child !== null); - - if (matches || (children && children.length > 0)) { - return { - ...node, - children, - expanded: children && children.length > 0 ? true : node.expanded, - }; - } - - return null; - }; - - const groups = data - .map(node => filterNode(node)) - .filter((node): node is SeasonNode => node !== null); - return {groups, firstMatch}; - }); - - filteredGroups = computed(() => this.filteredData().groups); - firstMatchingOption = computed(() => this.filteredData().firstMatch); + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); + }); + } - onCommit() { - const treeInstance = this.tree(); - if (!treeInstance) return; + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; - const activeItem = treeInstance._pattern.activeItem(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; - if (activeItem) { - if (activeItem.selectable()) { - // Selectable child: commit value and close popup. - const selected = this.selectedValues(); - if (selected.length > 0) { - this.searchString.set(selected[0]); - this.popupExpanded.set(false); - } - } else { - // Non-selectable parent: expand and focus its first child. - const children = activeItem.children(); - if (children.length > 0) { - const firstChild = children[0]; - treeInstance._pattern.treeBehavior.goto(firstChild); - } - } + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } + + popover.nativeElement.showPopover(); } } - -const SEASON_DATA: SeasonNode[] = [ - {name: 'Winter', children: [{name: 'December'}, {name: 'January'}, {name: 'February'}]}, - {name: 'Spring', children: [{name: 'March'}, {name: 'April'}, {name: 'May'}]}, - {name: 'Summer', children: [{name: 'June'}, {name: 'July'}, {name: 'August'}]}, - {name: 'Fall', children: [{name: 'September'}, {name: 'October'}, {name: 'November'}]}, -]; diff --git a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html new file mode 100644 index 000000000000..41c67e436ab0 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html @@ -0,0 +1,63 @@ +
    +
    + search + +
    + +
    + +
      + +
    +
    +
    +
    + + + @for (node of nodes; track node.name) { +
  • + + {{ node.name }} + +
  • + + @if (node.children) { +
      + + + +
    + } + } +
    diff --git a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts new file mode 100644 index 000000000000..5a7967f17d9f --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts @@ -0,0 +1,104 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {TREE_NODES, TreeNode} from '../data'; +import {NgTemplateOutlet} from '@angular/common'; + +/** @title Combobox with tree popup and manual filtering. */ +@Component({ + selector: 'combobox-tree-manual-example', + templateUrl: 'combobox-tree-manual-example.html', + styleUrl: '../combobox-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Tree, + TreeItem, + TreeItemGroup, + NgTemplateOutlet, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxTreeManualExample { + popover = viewChild('popover'); + tree = viewChild>(Tree); + combobox = viewChild>(Combobox); + + searchString = signal(''); + + nodes = computed(() => this.filterTreeNodes(TREE_NODES)); + + firstMatch = computed(() => { + const flatNodes = this.flattenTreeNodes(this.nodes()); + const node = flatNodes.find(n => this.isMatch(n)); + return node?.name; + }); + + flattenTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.flatMap(node => { + return node.children ? [node, ...this.flattenTreeNodes(node.children)] : [node]; + }); + } + + filterTreeNodes(nodes: TreeNode[]): TreeNode[] { + return nodes.reduce((acc, node) => { + const children = node.children ? this.filterTreeNodes(node.children) : undefined; + if (this.isMatch(node) || (children && children.length > 0)) { + acc.push({...node, children}); + } + return acc; + }, [] as TreeNode[]); + } + + isMatch(node: TreeNode) { + return node.name.toLowerCase().includes(this.searchString().toLowerCase()); + } + + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); + }); + } + + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; + } + + popover.nativeElement.showPopover(); + } +} diff --git a/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.html b/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.html deleted file mode 100644 index 4e0afa6015fe..000000000000 --- a/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.html +++ /dev/null @@ -1,47 +0,0 @@ -
    -
    - search - -
    - -
    - {{filteredGroups().length === 0 ? 'No results found for ' + searchString() : ''}} -
    - - - -
    - @if (filteredGroups().length === 0) { -
    No results found
    - } -
      - -
    -
    -
    -
    -
    - - - @for (node of nodes; track node.name) { -
  • - - {{ node.name }} - -
  • - @if (node.children) { -
      - - - -
    - } - } -
    \ No newline at end of file diff --git a/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.ts b/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.ts deleted file mode 100644 index 28645fd349fe..000000000000 --- a/src/components-examples/aria/combobox/combobox-tree/combobox-tree-example.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; -import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; -import {Component, afterRenderEffect, computed, signal, viewChild, untracked} from '@angular/core'; -import {NgTemplateOutlet} from '@angular/common'; -import {OverlayModule} from '@angular/cdk/overlay'; - -interface SeasonNode { - name: string; - children?: SeasonNode[]; - expanded?: boolean; -} - -/** @title */ -@Component({ - selector: 'combobox-tree-example', - templateUrl: 'combobox-tree-example.html', - styleUrl: '../combobox-example.css', - imports: [ - Combobox, - ComboboxPopup, - ComboboxWidget, - NgTemplateOutlet, - Tree, - TreeItem, - TreeItemGroup, - OverlayModule, - ], -}) -export class ComboboxTreeExample { - readonly tree = viewChild(Tree); - - popupExpanded = signal(false); - searchString = signal(''); - selectedValues = signal([]); - - readonly dataSource = signal(SEASON_DATA); - - constructor() { - afterRenderEffect(() => { - const active = this.tree()?._pattern.activeItem(); - if (active) { - untracked(() => { - active.element()?.scrollIntoView({block: 'nearest'}); - }); - } - }); - } - - filteredGroups = computed(() => { - const search = this.searchString().trim().toLowerCase(); - const data = this.dataSource(); - - if (!search) { - return data; - } - - const filterNode = (node: SeasonNode): SeasonNode | null => { - const matches = node.name.toLowerCase().includes(search); - const children = node.children - ?.map(child => filterNode(child)) - .filter((child): child is SeasonNode => child !== null); - - if (matches || (children && children.length > 0)) { - return { - ...node, - children, - expanded: children && children.length > 0 ? true : node.expanded, - }; - } - - return null; - }; - - return data.map(node => filterNode(node)).filter((node): node is SeasonNode => node !== null); - }); - - onCommit() { - const selected = this.selectedValues(); - if (selected.length > 0) { - const value = selected[0]; - this.searchString.set(value); - this.popupExpanded.set(false); - } - } -} - -const SEASON_DATA: SeasonNode[] = [ - {name: 'Winter', children: [{name: 'December'}, {name: 'January'}, {name: 'February'}]}, - {name: 'Spring', children: [{name: 'March'}, {name: 'April'}, {name: 'May'}]}, - {name: 'Summer', children: [{name: 'June'}, {name: 'July'}, {name: 'August'}]}, - {name: 'Fall', children: [{name: 'September'}, {name: 'October'}, {name: 'November'}]}, -]; diff --git a/src/components-examples/aria/combobox/countries.ts b/src/components-examples/aria/combobox/countries.ts deleted file mode 100644 index 3d9c4ba4f079..000000000000 --- a/src/components-examples/aria/combobox/countries.ts +++ /dev/null @@ -1,197 +0,0 @@ -export const COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/src/components-examples/aria/combobox/data.ts b/src/components-examples/aria/combobox/data.ts new file mode 100644 index 000000000000..ecc8db3bc4aa --- /dev/null +++ b/src/components-examples/aria/combobox/data.ts @@ -0,0 +1,23 @@ +export interface TreeNode { + name: string; + children?: TreeNode[]; +} + +export const TREE_NODES = [ + { + name: 'Winter', + children: [{name: 'December'}, {name: 'January'}, {name: 'February'}], + }, + { + name: 'Spring', + children: [{name: 'March'}, {name: 'April'}, {name: 'May'}], + }, + { + name: 'Summer', + children: [{name: 'June'}, {name: 'July'}, {name: 'August'}], + }, + { + name: 'Fall', + children: [{name: 'September'}, {name: 'October'}, {name: 'November'}], + }, +]; diff --git a/src/components-examples/aria/combobox/index.ts b/src/components-examples/aria/combobox/index.ts index f2ebe1d8675d..e19dfd098d76 100644 --- a/src/components-examples/aria/combobox/index.ts +++ b/src/components-examples/aria/combobox/index.ts @@ -1,20 +1,13 @@ -export {ComboboxListboxExample} from './combobox-listbox/combobox-listbox-example'; -export {ComboboxTreeExample} from './combobox-tree/combobox-tree-example'; -export {ComboboxSelectExample} from './combobox-select/combobox-select-example'; -export {ComboboxGridExample} from './combobox-grid/combobox-grid-example'; -export {ComboboxDatepickerExample} from './combobox-datepicker/combobox-datepicker-example'; +export {ComboboxDialogExample} from './combobox-dialog/combobox-dialog-example'; +export {ComboboxManualExample} from './combobox-manual/combobox-manual-example'; export {ComboboxAutoSelectExample} from './combobox-auto-select/combobox-auto-select-example'; export {ComboboxHighlightExample} from './combobox-highlight/combobox-highlight-example'; export {ComboboxDisabledExample} from './combobox-disabled/combobox-disabled-example'; + export {ComboboxReadonlyExample} from './combobox-readonly/combobox-readonly-example'; -export {ComboboxHardDisabledExample} from './combobox-hard-disabled/combobox-hard-disabled-example'; -export {ComboboxReadonlyDisabledExample} from './combobox-readonly-disabled/combobox-readonly-disabled-example'; export {ComboboxReadonlyMultiselectExample} from './combobox-readonly-multiselect/combobox-readonly-multiselect-example'; -export {ComboboxDialogExample} from './combobox-dialog/combobox-dialog-example'; +export {ComboboxReadonlyDisabledExample} from './combobox-readonly-disabled/combobox-readonly-disabled-example'; + +export {ComboboxTreeManualExample} from './combobox-tree-manual/combobox-tree-manual-example'; export {ComboboxTreeAutoSelectExample} from './combobox-tree-auto-select/combobox-tree-auto-select-example'; export {ComboboxTreeHighlightExample} from './combobox-tree-highlight/combobox-tree-highlight-example'; -export {ComboboxMultiselectDialogExample} from './combobox-multiselect-dialog/combobox-multiselect-dialog-example'; - -// Force watcher update - -export {ComboboxEditableMultiselectExample} from './combobox-editable-multiselect/combobox-editable-multiselect-example'; diff --git a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.css b/src/components-examples/aria/combobox/select-examples.css similarity index 69% rename from src/components-examples/aria/combobox/combobox-select/combobox-select-example.css rename to src/components-examples/aria/combobox/select-examples.css index bdff7dd05f3e..2e4dde0cd4d1 100644 --- a/src/components-examples/aria/combobox/combobox-select/combobox-select-example.css +++ b/src/components-examples/aria/combobox/select-examples.css @@ -6,38 +6,43 @@ font-size: var(--mat-sys-label-large); background-color: var(--mat-sys-primary); border-radius: var(--mat-sys-corner-extra-large); - padding: 0 2rem; - height: 3rem; - cursor: pointer; - user-select: none; - outline: none; +} + +.example-select:has([ngComboboxInput][aria-disabled='true']) { + opacity: 0.6; + cursor: default; } .example-select:hover { background-color: color-mix(in srgb, var(--mat-sys-primary) 90%, transparent); } -.example-select:focus { - outline-offset: 2px; - outline: 2px solid var(--mat-sys-primary); +[ngComboboxInput] { + opacity: 0; + cursor: pointer; + padding: 0 3rem; + height: 3rem; + border: none; } -.example-select[aria-disabled='true'] { - opacity: 0.4; - cursor: default; - pointer-events: none; +[ngCombobox]:focus-within .example-select { + outline-offset: 2px; + outline: 2px solid var(--mat-sys-primary); } .example-combobox-text { - width: 9rem; + left: 2rem; + position: absolute; } .example-arrow { + right: 1rem; + position: absolute; pointer-events: none; transition: transform 150ms ease-in-out; } -[ngCombobox][aria-expanded='true'] .example-arrow { +[ngComboboxInput][aria-expanded='true'] ~ .example-arrow { transform: rotate(180deg); } @@ -47,15 +52,30 @@ margin-top: 8px; border-radius: var(--mat-sys-corner-large); background-color: var(--mat-sys-surface-container); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + + max-height: 13rem; + opacity: 1; + visibility: visible; + transition: max-height 150ms ease-out, visibility 0s, opacity 25ms ease-out; } [ngListbox] { gap: 4px; + height: 100%; display: flex; overflow: auto; flex-direction: column; - max-height: 13rem; +} + +[ngCombobox]:has([ngComboboxInput][aria-expanded='false']) .example-popup-container { + max-height: 0; + opacity: 0; + visibility: hidden; + transition: max-height 150ms ease-in, visibility 0s 150ms, opacity 150ms ease-in; +} + +[ngCombobox]:has([ngComboboxInput][aria-expanded='true']) [ngListbox] { + display: flex; } [ngOption] { @@ -75,8 +95,6 @@ [ngOption][data-active='true'] { background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); - outline: 2px solid var(--mat-sys-on-surface); - outline-offset: -2px; } [ngOption][aria-selected='true'] { diff --git a/src/components-examples/aria/combobox/states.ts b/src/components-examples/aria/combobox/states.ts deleted file mode 100644 index db209524863a..000000000000 --- a/src/components-examples/aria/combobox/states.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export const STATES = [ - 'Alabama', - 'Alaska', - 'Arizona', - 'Arkansas', - 'California', - 'Colorado', - 'Connecticut', - 'Delaware', - 'Florida', - 'Georgia', - 'Hawaii', - 'Idaho', - 'Illinois', - 'Indiana', - 'Iowa', - 'Kansas', - 'Kentucky', - 'Louisiana', - 'Maine', - 'Maryland', - 'Massachusetts', - 'Michigan', - 'Minnesota', - 'Mississippi', - 'Missouri', - 'Montana', - 'Nebraska', - 'Nevada', - 'New Hampshire', - 'New Jersey', - 'New Mexico', - 'New York', - 'North Carolina', - 'North Dakota', - 'Ohio', - 'Oklahoma', - 'Oregon', - 'Pennsylvania', - 'Rhode Island', - 'South Carolina', - 'South Dakota', - 'Tennessee', - 'Texas', - 'Utah', - 'Vermont', - 'Virginia', - 'Washington', - 'West Virginia', - 'Wisconsin', - 'Wyoming', -]; diff --git a/src/components-examples/aria/grid/BUILD.bazel b/src/components-examples/aria/grid/BUILD.bazel index e88d2229c657..b16742afd8b4 100644 --- a/src/components-examples/aria/grid/BUILD.bazel +++ b/src/components-examples/aria/grid/BUILD.bazel @@ -14,12 +14,10 @@ ng_project( "//:node_modules/@angular/core", "//:node_modules/@angular/forms", "//src/aria/grid", - "//src/material/button", "//src/material/checkbox", "//src/material/form-field", "//src/material/icon", "//src/material/input", - "//src/material/menu", "//src/material/select", ], ) diff --git a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html index cf12acb21369..63d85bd843f0 100644 --- a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html +++ b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html @@ -18,7 +18,6 @@ [enableSelection]="true" [softDisabled]="false" selectionMode="explicit" - (keydown)="onKeyDown($event)" > @@ -45,7 +44,6 @@ ngGridCellWidget class="example-calendar-day-button" [attr.aria-label]="day.ariaLabel" - [attr.data-day]="day.dayNum" > {{day.displayName}} diff --git a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts index 54e0667daf3b..7f1e844e48b6 100644 --- a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts +++ b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts @@ -14,7 +14,6 @@ import { computed, untracked, afterRenderEffect, - viewChildren, } from '@angular/core'; import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; @@ -26,7 +25,6 @@ interface CalendarCell { ariaLabel: string; date: D; selected: WritableSignal; - dayNum: number; } /** @title Grid Calendar. */ @@ -37,8 +35,6 @@ interface CalendarCell { imports: [Grid, GridRow, GridCell, GridCellWidget], }) export class GridCalendarExample { - private readonly _dayButtons = viewChildren(GridCellWidget); - private readonly _dateAdapter = inject>(DateAdapter, {optional: true})!; private readonly _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; private readonly _firstWeekOffset: Signal = computed(() => { @@ -66,9 +62,6 @@ export class GridCalendarExample { .format(this.viewMonth(), this._dateFormats.display.monthYearLabel) .toLocaleUpperCase(), ); - readonly viewMonthNumDays: Signal = computed(() => - this._dateAdapter.getNumDaysInMonth(this.viewMonth()), - ); readonly prevMonthNumDays: Signal = computed(() => this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)), ); @@ -95,6 +88,9 @@ export class GridCalendarExample { this._createWeekCells(this.viewMonth()), ); + readonly scrolledUp = signal(false); + readonly scrolledDown = signal(false); + constructor() { afterRenderEffect(() => { for (const day of this.weeks().flat()) { @@ -114,40 +110,8 @@ export class GridCalendarExample { this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)); } - scrollDown(): void { - this.nextMonth(); - setTimeout(() => this._dayButtons()[0]?.element.focus()); - } - - scrollUp(): void { - this.prevMonth(); - setTimeout(() => this._dayButtons()[this._dayButtons().length - 1]?.element.focus()); - } - - onKeyDown(event: KeyboardEvent): void { - const day = Number((event.target as Element).getAttribute('data-day')); - if (!day) return; - if (day > 7 && day <= this.viewMonthNumDays() - 7) return; - - const arrowLeft = event.key === 'ArrowLeft'; - const arrowRight = event.key === 'ArrowRight'; - const arrowUp = event.key === 'ArrowUp'; - const arrowDown = event.key === 'ArrowDown'; - - if ((day === 1 && arrowLeft) || (day <= 7 && arrowUp)) { - this.scrollUp(); - } - - if ( - (day === this.viewMonthNumDays() && arrowRight) || - (day > this.viewMonthNumDays() - 7 && arrowDown) - ) { - this.scrollDown(); - } - } - private _createWeekCells(viewMonth: D): CalendarCell[][] { - const daysInMonth = this.viewMonthNumDays(); + const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth); const dateNames = this._dateAdapter.getDateNames(); const weeks: CalendarCell[][] = [[]]; for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) { @@ -172,7 +136,6 @@ export class GridCalendarExample { untracked(() => this._activeDate()), ) === 0, ), - dayNum: i + 1, }); } return weeks; diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html index a6111657fb34..c56e1e022c87 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html @@ -3,6 +3,7 @@ Soft Disabled Enable Selection Multi + Range Selection @for (row of gridData; track row) { @@ -115,10 +117,15 @@ @if (this.selectionMode === 'follow') {
  • Selection change on navigation (follow focus)
  • } - @if (multi.value) { + @if (multi.value && enableRangeSelection.value) { +
  • Shift + Arrow: expand selection
  • Ctrl + A: select all or deselect all (if all selected)
  • Shift + Space: select a row
  • Ctrl + Space: select a col
  • +
  • Shift + Home: range select from first cell in the row
  • +
  • Shift + End: range select to last cell in the row
  • +
  • Shift + Ctrl + Home: range select from very first cell
  • +
  • Shift + Ctrl + End: range select to very last cell
  • } @@ -133,6 +140,16 @@
  • Ctrl + Click: add/remove a cell
  • } } + @if (multi.value && enableRangeSelection.value) { + @if (this.selectionMode === 'explicit') { +
  • Drag: add a new range selection (explicit)
  • + } + @if (this.selectionMode === 'follow') { +
  • Drag: start a new range selection (follow focus)
  • + } +
  • Shift + Drag: update the current range selection
  • +
  • Ctrl + Drag: add a new range selection
  • + } }
    diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts index f4734ff71977..59611bda697c 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts @@ -103,6 +103,7 @@ export class GridConfigurableExample { softDisabled = new FormControl(false, {nonNullable: true}); enableSelection = new FormControl(false, {nonNullable: true}); multi = new FormControl(false, {nonNullable: true}); + enableRangeSelection = new FormControl(false, {nonNullable: true}); gridData: Cell[][] = generateValidGrid( 10, diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.css b/src/components-examples/aria/grid/grid-table/grid-table-example.css index 09c3534e646b..6ab620cd0b4a 100644 --- a/src/components-examples/aria/grid/grid-table/grid-table-example.css +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.css @@ -71,4 +71,3 @@ .example-hidden { display: none; } - diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.html b/src/components-examples/aria/grid/grid-table/grid-table-example.html index ca014fe81d37..637e79dff9ce 100644 --- a/src/components-examples/aria/grid/grid-table/grid-table-example.html +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.html @@ -1,7 +1,7 @@ - - - - - - + + + @@ -36,26 +34,24 @@ role="button" ngGridCellWidget widgetType="editable" - [focusTarget]="findSummaryInput" - (activated)="startEdit($event, task)" + (activated)="startEdit($event, task, summaryInput)" (deactivated)="completeEdit($event, task)" + (click)="onClickEdit(widget, task, summaryInput)" #widget="ngGridCellWidget" > - @if (!widget.isActivated()) { - {{task.summary()}} - - } @else { - - - - } + {{task.summary()}} + + + + - - } diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.ts b/src/components-examples/aria/grid/grid-table/grid-table-example.ts index 3f1fb63c79a1..35337e74688f 100644 --- a/src/components-examples/aria/grid/grid-table/grid-table-example.ts +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.ts @@ -11,8 +11,6 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; -import {MatButtonModule} from '@angular/material/button'; -import {MatMenuModule} from '@angular/material/menu'; import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; import {GridChips} from './grid-chips'; @@ -35,8 +33,6 @@ interface TaskRow { MatFormFieldModule, MatSelectModule, MatInputModule, - MatButtonModule, - MatMenuModule, Grid, GridRow, GridCell, @@ -63,11 +59,15 @@ export class GridTableExample { readonly tasks: WritableSignal = signal(this._createRows()); - findSummaryInput = (host: HTMLElement) => - host.querySelector('input.summary-input'); + constructor() {} - startEdit(event: KeyboardEvent | FocusEvent | undefined, task: TaskRow): void { + startEdit( + event: KeyboardEvent | FocusEvent | undefined, + task: TaskRow, + inputEl: HTMLInputElement, + ): void { this.tempInput.set(task.summary()); + inputEl.focus(); if (!(event instanceof KeyboardEvent)) return; @@ -77,6 +77,13 @@ export class GridTableExample { } } + onClickEdit(widget: GridCellWidget, task: TaskRow, inputEl: HTMLInputElement) { + if (widget.isActivated()) return; + + widget.activate(); + setTimeout(() => this.startEdit(undefined, task, inputEl)); + } + completeEdit(event: KeyboardEvent | FocusEvent | undefined, task: TaskRow): void { if (!(event instanceof KeyboardEvent)) { return; @@ -86,18 +93,20 @@ export class GridTableExample { } } - viewDetails(task: TaskRow) { - alert(`Viewing details for task: ${task.summary()}`); - } - - deleteTask(task: TaskRow) { - this.tasks.update(tasks => tasks.filter(t => t !== task)); - } - updateSelection(checked: boolean): void { this.tasks().forEach(t => t.selected.set(checked)); } + addTag(event: KeyboardEvent | FocusEvent | undefined, task: TaskRow, inputEl: HTMLInputElement) { + if (event instanceof KeyboardEvent && event.key === 'Enter') { + const value = inputEl.value; + if (value.length > 0) { + task.tags.set([...task.tags(), value]); + } + } + inputEl.value = ''; + } + private _createRows(): TaskRow[] { return [ { diff --git a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html index f39e842e284e..e979b8fb8fd3 100644 --- a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html +++ b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html @@ -52,7 +52,7 @@
      - - - -
      - - - - -
      - -
      - star - Item 1 -
      -
      - settings - Item 2 -
      -
      - help - Item 3 -
      -
      -
      -
      -
      -
      diff --git a/src/components-examples/aria/menu/menu-cdk-overlay/menu-cdk-overlay-example.ts b/src/components-examples/aria/menu/menu-cdk-overlay/menu-cdk-overlay-example.ts deleted file mode 100644 index daad39df0104..000000000000 --- a/src/components-examples/aria/menu/menu-cdk-overlay/menu-cdk-overlay-example.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Component, ViewChild} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {Menu, MenuTrigger, MenuContent} from '@angular/aria/menu'; -import {SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from '../simple-menu'; - -/** - * @title Menu CDK overlay example - */ -@Component({ - selector: 'menu-cdk-overlay-example', - templateUrl: 'menu-cdk-overlay-example.html', - styleUrl: '../menu-example.css', - imports: [ - CommonModule, - OverlayModule, - Menu, - MenuTrigger, - MenuContent, - SimpleMenuItem, - SimpleMenuItemIcon, - SimpleMenuItemText, - ], -}) -export class MenuCdkOverlayExample { - @ViewChild('myMenu') myMenu!: Menu; -} diff --git a/src/components-examples/aria/menu/menu-example.css b/src/components-examples/aria/menu/menu-example.css index 60842508a80e..6aaf76c9d91a 100644 --- a/src/components-examples/aria/menu/menu-example.css +++ b/src/components-examples/aria/menu/menu-example.css @@ -77,9 +77,7 @@ .example-menu-trigger:hover, .example-menu-item[data-active='true'], -.example-menu-bar-item:hover, -.example-menu-bar-item:focus-within, -.example-menu-bar-item[aria-expanded='true'] { +.example-menu-bar-item[data-active='true'] { background: color-mix( in srgb, var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%), diff --git a/src/components-examples/aria/menubar/menubar-disabled/menubar-disabled-example.html b/src/components-examples/aria/menubar/menubar-disabled/menubar-disabled-example.html index 0a0505ab1ef8..f12ccbaa7379 100644 --- a/src/components-examples/aria/menubar/menubar-disabled/menubar-disabled-example.html +++ b/src/components-examples/aria/menubar/menubar-disabled/menubar-disabled-example.html @@ -14,7 +14,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -48,7 +47,6 @@ [cdkConnectedOverlayOpen]="!!fileItem.expanded()" [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -106,7 +104,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -169,7 +166,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -220,7 +216,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -235,7 +230,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -273,7 +267,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -324,7 +317,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -339,7 +331,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -379,7 +370,6 @@ [cdkConnectedOverlayOpen]="!!textItem.expanded()" [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -410,7 +400,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -432,7 +421,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      diff --git a/src/components-examples/aria/menubar/menubar-rtl/menubar-rtl-example.html b/src/components-examples/aria/menubar/menubar-rtl/menubar-rtl-example.html index 3f130f7bc8a4..1c8a8a3c2868 100644 --- a/src/components-examples/aria/menubar/menubar-rtl/menubar-rtl-example.html +++ b/src/components-examples/aria/menubar/menubar-rtl/menubar-rtl-example.html @@ -14,7 +14,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -48,7 +47,6 @@ [cdkConnectedOverlayOpen]="!!fileItem.expanded()" [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -106,7 +104,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -169,7 +166,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -220,7 +216,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -235,7 +230,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -273,7 +267,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -324,7 +317,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -339,7 +331,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -379,7 +370,6 @@ [cdkConnectedOverlayOpen]="!!textItem.expanded()" [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -410,7 +400,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -432,7 +421,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      diff --git a/src/components-examples/aria/menubar/menubar.css b/src/components-examples/aria/menubar/menubar.css index 5f556e40c094..59050cb8a0de 100644 --- a/src/components-examples/aria/menubar/menubar.css +++ b/src/components-examples/aria/menubar/menubar.css @@ -47,13 +47,7 @@ opacity: 0.5; } -[ngMenu] [ngMenuItem][data-active='true'] { - background: color-mix(in srgb, var(--mat-sys-outline) 20%, transparent); -} - -[ngMenuBar] > [ngMenuItem]:hover, -[ngMenuBar] > [ngMenuItem]:focus-within, -[ngMenuBar] > [ngMenuItem][aria-expanded='true'] { +[ngMenuItem][data-active='true'] { background: color-mix(in srgb, var(--mat-sys-outline) 20%, transparent); } diff --git a/src/components-examples/aria/menubar/menubar/menubar-example.html b/src/components-examples/aria/menubar/menubar/menubar-example.html index f514e32d3597..0648bb77890b 100644 --- a/src/components-examples/aria/menubar/menubar/menubar-example.html +++ b/src/components-examples/aria/menubar/menubar/menubar-example.html @@ -14,7 +14,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -48,7 +47,6 @@ [cdkConnectedOverlayOpen]="!!fileItem.expanded()" [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -106,7 +104,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -169,7 +166,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -220,7 +216,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -235,7 +230,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -273,7 +267,6 @@ [cdkConnectedOverlayOpen]="!!insertItem.expanded()" [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -324,7 +317,6 @@ [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -339,7 +331,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -379,7 +370,6 @@ [cdkConnectedOverlayOpen]="!!textItem.expanded()" [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -410,7 +400,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      @@ -432,7 +421,6 @@ [cdkConnectedOverlayOpen]="!!formatItem.expanded()" [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}" [cdkConnectedOverlayPositions]="[{originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6}]" - [cdkConnectedOverlayDisableClose]="true" cdkAttachPopoverAsChild >
      diff --git a/src/components-examples/aria/select/BUILD.bazel b/src/components-examples/aria/select/BUILD.bazel new file mode 100644 index 000000000000..cf82f9bf6ef9 --- /dev/null +++ b/src/components-examples/aria/select/BUILD.bazel @@ -0,0 +1,27 @@ +load("//tools:defaults.bzl", "ng_project") + +package(default_visibility = ["//visibility:public"]) + +ng_project( + name = "select", + srcs = glob(["**/*.ts"]), + assets = glob([ + "**/*.html", + "**/*.css", + ]), + deps = [ + "//:node_modules/@angular/core", + "//src/aria/combobox", + "//src/aria/listbox", + "//src/cdk/overlay", + ], +) + +filegroup( + name = "source-files", + srcs = glob([ + "**/*.html", + "**/*.css", + "**/*.ts", + ]), +) diff --git a/src/components-examples/aria/select/index.ts b/src/components-examples/aria/select/index.ts new file mode 100644 index 000000000000..2ceef481d338 --- /dev/null +++ b/src/components-examples/aria/select/index.ts @@ -0,0 +1,3 @@ +export {SelectDisabledExample} from './select-disabled/select-disabled-example'; +export {SelectMultiExample} from './select-multi/select-multi-example'; +export {SelectExample} from './select/select-example'; diff --git a/src/components-examples/aria/select/select-disabled/select-disabled-example.html b/src/components-examples/aria/select/select-disabled/select-disabled-example.html new file mode 100644 index 000000000000..a64296a5d06d --- /dev/null +++ b/src/components-examples/aria/select/select-disabled/select-disabled-example.html @@ -0,0 +1,28 @@ +
      +
      +
      + Select an option +
      + + arrow_drop_down +
      + + + +
      +
      + @for (item of items; track item) { +
      + + {{item}} + +
      + } +
      +
      +
      +
      +
      diff --git a/src/components-examples/aria/select/select-disabled/select-disabled-example.ts b/src/components-examples/aria/select/select-disabled/select-disabled-example.ts new file mode 100644 index 000000000000..aacc40f17b4d --- /dev/null +++ b/src/components-examples/aria/select/select-disabled/select-disabled-example.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; + +/** @title Aria select disabled example. */ +@Component({ + selector: 'select-disabled-example', + templateUrl: 'select-disabled-example.html', + styleUrl: '../select.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SelectDisabledExample { + /** The items available for selection. */ + items = ['Option 1', 'Option 2', 'Option 3']; +} diff --git a/src/components-examples/aria/select/select-multi/select-multi-example.html b/src/components-examples/aria/select/select-multi/select-multi-example.html new file mode 100644 index 000000000000..29e40ca53586 --- /dev/null +++ b/src/components-examples/aria/select/select-multi/select-multi-example.html @@ -0,0 +1,27 @@ +
      +
      +
      + {{ displayValue() }} +
      + + arrow_drop_down +
      + + + +
      +
      + @for (item of items; track item) { +
      + {{item}} + +
      + } +
      +
      +
      +
      +
      diff --git a/src/components-examples/aria/select/select-multi/select-multi-example.ts b/src/components-examples/aria/select/select-multi/select-multi-example.ts new file mode 100644 index 000000000000..8d553c4a84ac --- /dev/null +++ b/src/components-examples/aria/select/select-multi/select-multi-example.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; + +/** @title Aria multiselect example. */ +@Component({ + selector: 'select-multi-example', + templateUrl: 'select-multi-example.html', + styleUrl: '../select.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SelectMultiExample { + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The visible label displayed to the user. */ + displayValue = computed(() => { + const values = this.listbox()?.values(); + + if (!values?.length) { + return 'Select a day'; + } + + if (values.length <= 2) { + return values.join(', '); + } + + return `${values[0]} + ${values.length - 1} more`; + }); + + /** The items available for selection. */ + items = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + + constructor() { + // Scrolls to the active item when the active option changes. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); + }); + } +} diff --git a/src/components-examples/aria/select/select.css b/src/components-examples/aria/select/select.css new file mode 100644 index 000000000000..282b043531f1 --- /dev/null +++ b/src/components-examples/aria/select/select.css @@ -0,0 +1,137 @@ +.example-select { + position: relative; + display: flex; + align-items: center; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); + + /* stylelint-disable-next-line material/no-prefixes -- Valid in all remotely recent browsers. */ + width: fit-content; +} + +.example-select:has([ngComboboxInput][aria-disabled='true']) { + opacity: 0.7; + background-color: var(--mat-sys-surface-dim); +} + +.example-select:focus-within { + outline-offset: -2px; + outline: 2px solid var(--mat-sys-primary); +} + +.example-arrow, +.example-select-value { + position: absolute; + pointer-events: none; +} + +.example-select-value { + display: flex; + gap: 1rem; + left: 1rem; + width: calc(100% - 4rem); +} + +.example-select-label { + text-overflow: ellipsis; + text-wrap-mode: nowrap; + overflow: hidden; +} + +.example-arrow, +.example-select-icon { + font-size: 1.25rem; + opacity: 0.875; +} + +.example-arrow { + right: 1rem; + transition: transform 0.2s ease-in-out; +} + +[ngComboboxInput] { + cursor: pointer; + padding: 0.7rem 3rem; + opacity: 0; +} + +[ngComboboxInput][aria-disabled='true'] { + cursor: default; +} + +[ngComboboxInput][aria-expanded='true'] + .example-arrow { + transform: rotate(180deg); +} + +[ngCombobox]:has([aria-expanded='false']) .example-popup { + display: none; +} + +.example-popup { + width: 100%; + margin-top: 2px; + padding: 0.1rem; + max-height: 11rem; + border-radius: var(--mat-sys-corner-extra-small); + background-color: var(--mat-sys-surface); + border: 1px solid var(--mat-sys-outline); +} + +.example-no-results { + padding: 1rem; +} + +[ngListbox] { + gap: 2px; + width: 100%; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; +} + +[ngOption] { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + gap: 1rem; + padding: 0.7rem 1rem; + border-radius: var(--mat-sys-corner-extra-small); +} + +[ngOption][aria-disabled='true'] { + cursor: default; + opacity: 0.5; + background-color: var(--mat-sys-surface-dim); +} + +[ngOption]:hover { + background-color: color-mix(in srgb, var(--mat-sys-outline) 15%, transparent); +} + +[ngOption][data-active='true'] { + outline-offset: -2px; + outline: 2px solid var(--mat-sys-primary); +} + +[ngOption][aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +[ngOption]:not([aria-selected='true']) .example-option-check { + display: none; +} + +.example-option-text { + flex: 1; +} + +.example-option-icon { + font-size: 1.25rem; +} + +.example-option-check { + font-size: 1rem; +} diff --git a/src/components-examples/aria/select/select/select-example.html b/src/components-examples/aria/select/select/select-example.html new file mode 100644 index 000000000000..9dfd34de9b9f --- /dev/null +++ b/src/components-examples/aria/select/select/select-example.html @@ -0,0 +1,35 @@ +
      +
      +
      + {{ value().icon }} + {{ value().label }} +
      + + arrow_drop_down +
      + + + +
      +
      + @for (item of items; track item.label) { +
      + + {{item.label}} + +
      + } +
      +
      +
      +
      +
      diff --git a/src/components-examples/aria/select/select/select-example.ts b/src/components-examples/aria/select/select/select-example.ts new file mode 100644 index 000000000000..9a57cc865e74 --- /dev/null +++ b/src/components-examples/aria/select/select/select-example.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; + +/** @title Aria select example. */ +@Component({ + selector: 'select-example', + templateUrl: 'select-example.html', + styleUrl: '../select.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SelectExample { + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The current value of the select. */ + value = computed(() => this.listbox()?.values()[0] ?? this.items[1]); + + /** The items available for selection. */ + items = [ + {label: 'Light Mode', icon: 'light_mode'}, + {label: 'Dark Mode', icon: 'dark_mode'}, + {label: 'System Default', icon: 'settings'}, + ]; + + constructor() { + // Scrolls to the active item when the active option changes. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + option?.element.scrollIntoView({block: 'nearest'}); + }); + } +} diff --git a/src/components-examples/aria/tabs/index.ts b/src/components-examples/aria/tabs/index.ts index ed4d17e56227..e0eb95b5a5e1 100644 --- a/src/components-examples/aria/tabs/index.ts +++ b/src/components-examples/aria/tabs/index.ts @@ -7,4 +7,3 @@ export {TabsActiveDescendantExample} from './active-descendant/tabs-active-desce export {TabsDisabledFocusableExample} from './disabled-focusable/tabs-disabled-focusable-example'; export {TabsDisabledSkippedExample} from './disabled-skipped/tabs-disabled-skipped-example'; export {TabsDisabledExample} from './disabled/tabs-disabled-example'; -export {TabsScrollableExample} from './scrollable/tabs-scrollable-example'; diff --git a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.css b/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.css deleted file mode 100644 index 9ed26043f6a5..000000000000 --- a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.css +++ /dev/null @@ -1,7 +0,0 @@ -/* Custom styles for the scrollable example */ - -/* Ensure the example container doesn't expand infinitely */ -tabs-scrollable-example { - display: block; - max-width: 100%; -} diff --git a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html b/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html deleted file mode 100644 index 05407c3d0773..000000000000 --- a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.html +++ /dev/null @@ -1,13 +0,0 @@ -
      -
      - @for (i of tabsList; track i) { -
      Tab {{i}}
      - } -
      - - @for (i of tabsList; track i) { -
      - Content for Tab {{i}} -
      - } -
      diff --git a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.ts b/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.ts deleted file mode 100644 index 01e4778cb651..000000000000 --- a/src/components-examples/aria/tabs/scrollable/tabs-scrollable-example.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {afterRenderEffect, Component, viewChildren} from '@angular/core'; -import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; - -/** @title Scrollable tabs */ -@Component({ - selector: 'tabs-scrollable-example', - templateUrl: 'tabs-scrollable-example.html', - styleUrls: ['../tabs-common.css', 'tabs-scrollable-example.css'], - imports: [TabList, Tab, Tabs, TabPanel, TabContent], -}) -export class TabsScrollableExample { - tabs = viewChildren(Tab); - tabsList = Array.from({length: 15}, (_, i) => i + 1); - - constructor() { - afterRenderEffect(() => { - const tab = this.tabs().find(t => t.active()); - tab?.element.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'nearest'}); - }); - } -} diff --git a/src/components-examples/aria/toolbar/simple-toolbar.ts b/src/components-examples/aria/toolbar/simple-toolbar.ts index 76094c8cb32a..56ed29dbd183 100644 --- a/src/components-examples/aria/toolbar/simple-toolbar.ts +++ b/src/components-examples/aria/toolbar/simple-toolbar.ts @@ -1,13 +1,24 @@ -import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; import {ToolbarWidget} from '@angular/aria/toolbar'; import {Dir, Directionality} from '@angular/cdk/bidi'; -import {afterRenderEffect, Component, Directive, inject, signal, viewChild} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; +import { + afterRenderEffect, + Component, + Directive, + ElementRef, + inject, + signal, + viewChild, +} from '@angular/core'; @Directive({ selector: 'button[toolbar-button]', - standalone: true, hostDirectives: [{directive: ToolbarWidget, inputs: ['value', 'disabled']}], host: { type: 'button', @@ -21,7 +32,6 @@ export class SimpleToolbarButton { @Directive({ selector: 'button[toolbar-toggle-button]', - standalone: true, hostDirectives: [{directive: ToolbarWidget, inputs: ['value']}], host: { type: 'button', @@ -36,7 +46,6 @@ export class SimpleToolbarToggleButton { @Directive({ selector: 'button[toolbar-radio-button]', - standalone: true, hostDirectives: [{directive: ToolbarWidget, inputs: ['value', 'disabled']}], host: { role: 'radio', @@ -52,45 +61,38 @@ export class SimpleToolbarRadioButton { @Component({ selector: 'combobox', - standalone: true, imports: [ Dir, Combobox, + ComboboxInput, ComboboxPopup, - ComboboxWidget, + ComboboxPopupContainer, Listbox, Option, ToolbarWidget, - OverlayModule, ], styleUrl: 'toolbar-common.css', host: {class: 'example-combobox-container'}, template: ` -
      -
      -
      - {{ value() }} -
      +
      +
      + arrow_drop_down
      - - -
      +
      + +
      @for (option of options; track option) { -
      +
      {{option}} - +
      `, }) -export class ToolbarCombobox { +export class SimpleCombobox { dir = inject(Directionality).valueSignal; - listbox = viewChild(Listbox); - combobox = viewChild(Combobox); + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); - popupExpanded = signal(false); - selectedOption = signal([]); value = signal('Normal text'); options = ['Normal text', 'Title', 'Subtitle', 'Heading 1', 'Heading 2', 'Heading 3']; constructor() { afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.listbox()?.scrollActiveItemIntoView(); }); } - onCommit() { - const selectedOption = this.selectedOption(); - if (selectedOption.length > 0) { - this.value.set(selectedOption[0]); + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; } - this.popupExpanded.set(false); + + popover.nativeElement.showPopover(); } } diff --git a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts index 993b5a8a9a2d..8ec2ff47c8c7 100644 --- a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts @@ -1,7 +1,7 @@ import {Component} from '@angular/core'; import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; import { - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, @@ -16,7 +16,7 @@ import { Toolbar, ToolbarWidget, ToolbarWidgetGroup, - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, diff --git a/src/components-examples/aria/toolbar/toolbar-common.css b/src/components-examples/aria/toolbar/toolbar-common.css index a1a8c398c4cf..b3d258f780bc 100644 --- a/src/components-examples/aria/toolbar/toolbar-common.css +++ b/src/components-examples/aria/toolbar/toolbar-common.css @@ -152,16 +152,6 @@ background-color: var(--mat-sys-surface); } -.example-popup { - width: 100%; - margin-block-start: 0.25rem; - border: 1px solid color-mix(in srgb, var(--mat-sys-outline) 50%, transparent); - border-radius: var(--mat-sys-corner-extra-small); - background-color: var(--mat-sys-surface); - max-height: 15rem; - overflow: auto; -} - .example-option { cursor: pointer; font-size: 0.8rem; diff --git a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts index 0aed3ae7f226..1306d9eea92e 100644 --- a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts @@ -5,7 +5,7 @@ import {MatSelectModule} from '@angular/material/select'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; import { - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, @@ -20,7 +20,7 @@ import { Toolbar, ToolbarWidget, ToolbarWidgetGroup, - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, diff --git a/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts index ffc1f14672ab..2d1c27e4863c 100644 --- a/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts @@ -1,7 +1,7 @@ import {Component} from '@angular/core'; import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; import { - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, @@ -16,7 +16,7 @@ import { Toolbar, ToolbarWidget, ToolbarWidgetGroup, - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, diff --git a/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts index b259c40b626b..ddfca5a98528 100644 --- a/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts @@ -2,7 +2,7 @@ import {Component} from '@angular/core'; import {Dir} from '@angular/cdk/bidi'; import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; import { - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, @@ -18,7 +18,7 @@ import { Toolbar, ToolbarWidget, ToolbarWidgetGroup, - ToolbarCombobox, + SimpleCombobox, SimpleToolbarButton, SimpleToolbarRadioButton, SimpleToolbarToggleButton, diff --git a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html index a624afc1c3a1..9fbccf4943c4 100644 --- a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html +++ b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html @@ -36,7 +36,7 @@ [wrap]="wrap.value" [softDisabled]="softDisabled.value" [nav]="nav.value" - [(value)]="selectedValues" + [(values)]="selectedValues" #tree="ngTree" > - diff --git a/src/components-examples/cdk/portal/cdk-portal-overview/cdk-portal-overview-example.css b/src/components-examples/cdk/portal/cdk-portal-overview/cdk-portal-overview-example.css index f20526befe91..6295d78b92a9 100644 --- a/src/components-examples/cdk/portal/cdk-portal-overview/cdk-portal-overview-example.css +++ b/src/components-examples/cdk/portal/cdk-portal-overview/cdk-portal-overview-example.css @@ -1,7 +1,7 @@ .example-portal-outlet { margin-bottom: 10px; padding: 10px; - border: 1px dashed white; + border: 1px dashed black; width: 250px; height: 250px; } diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-append-only/cdk-virtual-scroll-append-only-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-append-only/cdk-virtual-scroll-append-only-example.ts index 768359a9203a..766e0d354f8f 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-append-only/cdk-virtual-scroll-append-only-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-append-only/cdk-virtual-scroll-append-only-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scroll with view recycling disabled. */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-append-only-example', styleUrl: 'cdk-virtual-scroll-append-only-example.css', templateUrl: 'cdk-virtual-scroll-append-only-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollAppendOnlyExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-context/cdk-virtual-scroll-context-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-context/cdk-virtual-scroll-context-example.ts index 834935248811..6b2861feaf0d 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-context/cdk-virtual-scroll-context-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-context/cdk-virtual-scroll-context-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scroll context variables */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-context-example', styleUrl: 'cdk-virtual-scroll-context-example.css', templateUrl: 'cdk-virtual-scroll-context-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollContextExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-custom-strategy/cdk-virtual-scroll-custom-strategy-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-custom-strategy/cdk-virtual-scroll-custom-strategy-example.ts index f2c24184c280..ea1112130640 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-custom-strategy/cdk-virtual-scroll-custom-strategy-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-custom-strategy/cdk-virtual-scroll-custom-strategy-example.ts @@ -3,7 +3,7 @@ import { ScrollingModule, VIRTUAL_SCROLL_STRATEGY, } from '@angular/cdk/scrolling'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { constructor() { @@ -16,6 +16,7 @@ export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy selector: 'cdk-virtual-scroll-custom-strategy-example', styleUrl: 'cdk-virtual-scroll-custom-strategy-example.css', templateUrl: 'cdk-virtual-scroll-custom-strategy-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, providers: [{provide: VIRTUAL_SCROLL_STRATEGY, useClass: CustomVirtualScrollStrategy}], imports: [ScrollingModule], }) diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-data-source/cdk-virtual-scroll-data-source-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-data-source/cdk-virtual-scroll-data-source-example.ts index 3077998a7b6c..8f62a431911a 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-data-source/cdk-virtual-scroll-data-source-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-data-source/cdk-virtual-scroll-data-source-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {BehaviorSubject, Observable, Subscription} from 'rxjs'; import {ScrollingModule} from '@angular/cdk/scrolling'; @@ -8,6 +8,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-data-source-example', styleUrl: 'cdk-virtual-scroll-data-source-example.css', templateUrl: 'cdk-virtual-scroll-data-source-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollDataSourceExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-dl/cdk-virtual-scroll-dl-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-dl/cdk-virtual-scroll-dl-example.ts index 5d659fe9a466..698f11564ee5 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-dl/cdk-virtual-scroll-dl-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-dl/cdk-virtual-scroll-dl-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scrolling `
      ` */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-dl-example', styleUrl: 'cdk-virtual-scroll-dl-example.css', templateUrl: 'cdk-virtual-scroll-dl-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollDlExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-fixed-buffer/cdk-virtual-scroll-fixed-buffer-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-fixed-buffer/cdk-virtual-scroll-fixed-buffer-example.ts index 0c7e7cf0bc49..73dc19466ec1 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-fixed-buffer/cdk-virtual-scroll-fixed-buffer-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-fixed-buffer/cdk-virtual-scroll-fixed-buffer-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Fixed size virtual scroll with custom buffer parameters */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-fixed-buffer-example', styleUrl: 'cdk-virtual-scroll-fixed-buffer-example.css', templateUrl: 'cdk-virtual-scroll-fixed-buffer-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollFixedBufferExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-horizontal/cdk-virtual-scroll-horizontal-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-horizontal/cdk-virtual-scroll-horizontal-example.ts index b51ca55e782a..cb05c1f1069b 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-horizontal/cdk-virtual-scroll-horizontal-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-horizontal/cdk-virtual-scroll-horizontal-example.ts @@ -1,4 +1,4 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Horizontal virtual scroll */ @@ -7,6 +7,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; styleUrl: 'cdk-virtual-scroll-horizontal-example.css', templateUrl: 'cdk-virtual-scroll-horizontal-example.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollHorizontalExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-overview/cdk-virtual-scroll-overview-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-overview/cdk-virtual-scroll-overview-example.ts index 63bb9fd6b46b..cf2efdf3eb68 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-overview/cdk-virtual-scroll-overview-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-overview/cdk-virtual-scroll-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Basic virtual scroll */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-overview-example', styleUrl: 'cdk-virtual-scroll-overview-example.css', templateUrl: 'cdk-virtual-scroll-overview-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollOverviewExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-parent-scrolling/cdk-virtual-scroll-parent-scrolling-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-parent-scrolling/cdk-virtual-scroll-parent-scrolling-example.ts index 454ddf97ee28..e9b1a31f0301 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-parent-scrolling/cdk-virtual-scroll-parent-scrolling-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-parent-scrolling/cdk-virtual-scroll-parent-scrolling-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scrolling viewport parent element */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-parent-scrolling-example', styleUrl: 'cdk-virtual-scroll-parent-scrolling-example.css', templateUrl: 'cdk-virtual-scroll-parent-scrolling-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollParentScrollingExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-template-cache/cdk-virtual-scroll-template-cache-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-template-cache/cdk-virtual-scroll-template-cache-example.ts index 31a041eb2f43..88fce9417c17 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-template-cache/cdk-virtual-scroll-template-cache-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-template-cache/cdk-virtual-scroll-template-cache-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scroll with no template caching */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-template-cache-example', styleUrl: 'cdk-virtual-scroll-template-cache-example.css', templateUrl: 'cdk-virtual-scroll-template-cache-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollTemplateCacheExample { diff --git a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-window-scrolling/cdk-virtual-scroll-window-scrolling-example.ts b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-window-scrolling/cdk-virtual-scroll-window-scrolling-example.ts index 811ce57ab33e..d41d2f470ef3 100644 --- a/src/components-examples/cdk/scrolling/cdk-virtual-scroll-window-scrolling/cdk-virtual-scroll-window-scrolling-example.ts +++ b/src/components-examples/cdk/scrolling/cdk-virtual-scroll-window-scrolling/cdk-virtual-scroll-window-scrolling-example.ts @@ -1,4 +1,4 @@ -import {Component, input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; /** @title Virtual scrolling window */ @@ -6,6 +6,7 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; selector: 'cdk-virtual-scroll-window-scrolling-example', styleUrl: 'cdk-virtual-scroll-window-scrolling-example.css', templateUrl: 'cdk-virtual-scroll-window-scrolling-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ScrollingModule], }) export class CdkVirtualScrollWindowScrollingExample { diff --git a/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.ts b/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.ts index 4b3f3bdcb701..8fbd4f558482 100644 --- a/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.ts @@ -1,6 +1,6 @@ import {CdkTreeModule} from '@angular/cdk/tree'; import {AsyncPipe} from '@angular/common'; -import {Component, OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; @@ -248,6 +248,7 @@ class ComplexDataStore { templateUrl: 'cdk-tree-complex-example.html', styleUrls: ['cdk-tree-complex-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule, MatProgressSpinnerModule, AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeComplexExample implements OnInit { private readonly _dataStore = new ComplexDataStore(); diff --git a/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.ts b/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.ts index 36e6f8c805d6..bc66a7a4eb95 100644 --- a/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.ts @@ -1,4 +1,4 @@ -import {Component, QueryList} from '@angular/core'; +import {ChangeDetectionStrategy, Component, QueryList} from '@angular/core'; import {ArrayDataSource} from '@angular/cdk/collections'; import {coerceObservable} from '@angular/cdk/coercion/private'; import {FlatTreeControl, CdkTreeModule} from '@angular/cdk/tree'; @@ -313,6 +313,7 @@ const VIM_TREE_KEY_MANAGER_PROVIDER = { styleUrls: ['cdk-tree-custom-key-manager-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule], providers: [VIM_TREE_KEY_MANAGER_PROVIDER], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeCustomKeyManagerExample { treeControl = new FlatTreeControl( diff --git a/src/components-examples/cdk/tree/cdk-tree-flat-children-accessor/cdk-tree-flat-children-accessor-example.ts b/src/components-examples/cdk/tree/cdk-tree-flat-children-accessor/cdk-tree-flat-children-accessor-example.ts index 51a5383f6211..cb5ecbbd3d8e 100644 --- a/src/components-examples/cdk/tree/cdk-tree-flat-children-accessor/cdk-tree-flat-children-accessor-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-flat-children-accessor/cdk-tree-flat-children-accessor-example.ts @@ -1,6 +1,6 @@ import {ArrayDataSource} from '@angular/cdk/collections'; import {CdkTree, CdkTreeModule} from '@angular/cdk/tree'; -import {Component, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; import {timer} from 'rxjs'; @@ -34,6 +34,7 @@ function flattenNodes(nodes: NestedFoodNode[]): NestedFoodNode[] { templateUrl: 'cdk-tree-flat-children-accessor-example.html', styleUrls: ['cdk-tree-flat-children-accessor-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeFlatChildrenAccessorExample { @ViewChild(CdkTree) diff --git a/src/components-examples/cdk/tree/cdk-tree-flat-level-accessor/cdk-tree-flat-level-accessor-example.ts b/src/components-examples/cdk/tree/cdk-tree-flat-level-accessor/cdk-tree-flat-level-accessor-example.ts index d11f2200f78e..48b85f978b56 100644 --- a/src/components-examples/cdk/tree/cdk-tree-flat-level-accessor/cdk-tree-flat-level-accessor-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-flat-level-accessor/cdk-tree-flat-level-accessor-example.ts @@ -1,6 +1,6 @@ import {ArrayDataSource} from '@angular/cdk/collections'; import {CdkTree, CdkTreeModule} from '@angular/cdk/tree'; -import {Component, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; @@ -20,6 +20,7 @@ interface FlatFoodNode { templateUrl: 'cdk-tree-flat-level-accessor-example.html', styleUrls: ['cdk-tree-flat-level-accessor-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeFlatLevelAccessorExample { @ViewChild(CdkTree) diff --git a/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts b/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts index 9d7f687ac130..8cd723ace64d 100644 --- a/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ArrayDataSource} from '@angular/cdk/collections'; import {FlatTreeControl, CdkTreeModule} from '@angular/cdk/tree'; import {MatIconModule} from '@angular/material/icon'; @@ -20,6 +20,7 @@ interface ExampleFlatNode { templateUrl: 'cdk-tree-flat-example.html', styleUrl: 'cdk-tree-flat-example.css', imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeFlatExample { treeControl = new FlatTreeControl( diff --git a/src/components-examples/cdk/tree/cdk-tree-nested-children-accessor/cdk-tree-nested-children-accessor-example.ts b/src/components-examples/cdk/tree/cdk-tree-nested-children-accessor/cdk-tree-nested-children-accessor-example.ts index f06638f794f2..403e17c9909d 100644 --- a/src/components-examples/cdk/tree/cdk-tree-nested-children-accessor/cdk-tree-nested-children-accessor-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-nested-children-accessor/cdk-tree-nested-children-accessor-example.ts @@ -1,6 +1,6 @@ import {ArrayDataSource} from '@angular/cdk/collections'; import {CdkTree, CdkTreeModule} from '@angular/cdk/tree'; -import {Component, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; @@ -32,6 +32,7 @@ function flattenNodes(nodes: NestedFoodNode[]): NestedFoodNode[] { templateUrl: 'cdk-tree-nested-children-accessor-example.html', styleUrls: ['cdk-tree-nested-children-accessor-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeNestedChildrenAccessorExample { @ViewChild(CdkTree) tree!: CdkTree; diff --git a/src/components-examples/cdk/tree/cdk-tree-nested-level-accessor/cdk-tree-nested-level-accessor-example.ts b/src/components-examples/cdk/tree/cdk-tree-nested-level-accessor/cdk-tree-nested-level-accessor-example.ts index 2cb0f2517b04..7f45d54c4d51 100644 --- a/src/components-examples/cdk/tree/cdk-tree-nested-level-accessor/cdk-tree-nested-level-accessor-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-nested-level-accessor/cdk-tree-nested-level-accessor-example.ts @@ -1,6 +1,6 @@ import {ArrayDataSource} from '@angular/cdk/collections'; import {CdkTree, CdkTreeModule} from '@angular/cdk/tree'; -import {Component, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; @@ -20,6 +20,7 @@ interface FlatFoodNode { templateUrl: 'cdk-tree-nested-level-accessor-example.html', styleUrls: ['cdk-tree-nested-level-accessor-example.css'], imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeNestedLevelAccessorExample { @ViewChild(CdkTree) tree!: CdkTree; diff --git a/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts b/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts index a5e8b00ba884..622cb919236c 100644 --- a/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts +++ b/src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ArrayDataSource} from '@angular/cdk/collections'; import {NestedTreeControl, CdkTreeModule} from '@angular/cdk/tree'; import {MatIconModule} from '@angular/material/icon'; @@ -21,6 +21,7 @@ interface FoodNode { templateUrl: 'cdk-tree-nested-example.html', styleUrl: 'cdk-tree-nested-example.css', imports: [CdkTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTreeNestedExample { treeControl = new NestedTreeControl(node => node.children); diff --git a/src/components-examples/config.bzl b/src/components-examples/config.bzl index d7bc9e62c39a..435e064c5cb3 100644 --- a/src/components-examples/config.bzl +++ b/src/components-examples/config.bzl @@ -37,7 +37,6 @@ ALL_EXAMPLES = [ "//src/components-examples/material/bottom-sheet", "//src/components-examples/material/badge", "//src/components-examples/material/autocomplete", - "//src/components-examples/aria/autocomplete", "//src/components-examples/material/timepicker", "//src/components-examples/material-experimental/column-resize", "//src/components-examples/material-experimental/popover-edit", diff --git a/src/components-examples/material-experimental/column-resize/default-enabled-column-resize-flex/default-enabled-column-resize-flex-example.html b/src/components-examples/material-experimental/column-resize/default-enabled-column-resize-flex/default-enabled-column-resize-flex-example.html index 92d3b4cdf4c2..7e4768f218f2 100644 --- a/src/components-examples/material-experimental/column-resize/default-enabled-column-resize-flex/default-enabled-column-resize-flex-example.html +++ b/src/components-examples/material-experimental/column-resize/default-enabled-column-resize-flex/default-enabled-column-resize-flex-example.html @@ -1,4 +1,4 @@ - + No. diff --git a/src/components-examples/material-experimental/column-resize/default-enabled-column-resize/default-enabled-column-resize-example.html b/src/components-examples/material-experimental/column-resize/default-enabled-column-resize/default-enabled-column-resize-example.html index 456b2260ad8a..ca64fbb09959 100644 --- a/src/components-examples/material-experimental/column-resize/default-enabled-column-resize/default-enabled-column-resize-example.html +++ b/src/components-examples/material-experimental/column-resize/default-enabled-column-resize/default-enabled-column-resize-example.html @@ -1,4 +1,4 @@ -
    + SummaryAssigneeTagsActionsMoreSummaryAssigneeTags
    @@ -76,34 +72,28 @@ -
    - +
    +
    + +
    +
    - - - - - - -
    +
    diff --git a/src/components-examples/material-experimental/column-resize/opt-in-column-resize/opt-in-column-resize-example.html b/src/components-examples/material-experimental/column-resize/opt-in-column-resize/opt-in-column-resize-example.html index 078d26ece015..3761945f42c5 100644 --- a/src/components-examples/material-experimental/column-resize/opt-in-column-resize/opt-in-column-resize-example.html +++ b/src/components-examples/material-experimental/column-resize/opt-in-column-resize/opt-in-column-resize-example.html @@ -1,4 +1,4 @@ -
    No.
    +
    diff --git a/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts b/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts index 7677e7cf9395..83d293a5f15c 100644 --- a/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts +++ b/src/components-examples/material-experimental/popover-edit/popover-edit-tab-out-mat-table/popover-edit-tab-out-mat-table-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {DataSource} from '@angular/cdk/collections'; import {NgForm, FormsModule} from '@angular/forms'; import {BehaviorSubject, Observable} from 'rxjs'; @@ -45,6 +45,7 @@ const ELEMENT_DATA: PeriodicElement[] = [ selector: 'popover-edit-tab-out-mat-table-example', styleUrl: 'popover-edit-tab-out-mat-table-example.css', templateUrl: 'popover-edit-tab-out-mat-table-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatTableModule, FormsModule, diff --git a/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.html b/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.html index b923f7681290..e219ae709ab9 100644 --- a/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.html +++ b/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.html @@ -12,7 +12,7 @@ (input)="filter()" (focus)="filter()"> - @for (option of filteredOptions(); track option) { + @for (option of filteredOptions; track option) { {{option}} } diff --git a/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.ts b/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.ts index f63c100b94af..cdbea070ebea 100644 --- a/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.ts +++ b/src/components-examples/material/autocomplete/autocomplete-require-selection/autocomplete-require-selection-example.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, signal, viewChild} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatInputModule} from '@angular/material/input'; @@ -20,13 +20,13 @@ import {MatFormFieldModule} from '@angular/material/form-field'; ], }) export class AutocompleteRequireSelectionExample { - input = viewChild.required>('input'); + @ViewChild('input') input!: ElementRef; myControl = new FormControl(''); options: string[] = ['One', 'Two', 'Three', 'Four', 'Five']; - filteredOptions = signal(this.options.slice()); + filteredOptions: string[] = this.options.slice(); filter(): void { - const filterValue = this.input().nativeElement.value.toLowerCase(); - this.filteredOptions.set(this.options.filter(o => o.toLowerCase().includes(filterValue))); + const filterValue = this.input.nativeElement.value.toLowerCase(); + this.filteredOptions = this.options.filter(o => o.toLowerCase().includes(filterValue)); } } diff --git a/src/components-examples/material/badge/badge-overview/badge-overview-example.html b/src/components-examples/material/badge/badge-overview/badge-overview-example.html index 0dda2ec3a1bd..ba35dcacfb60 100644 --- a/src/components-examples/material/badge/badge-overview/badge-overview-example.html +++ b/src/components-examples/material/badge/badge-overview/badge-overview-example.html @@ -19,7 +19,7 @@
    Button toggles badge visibility - diff --git a/src/components-examples/material/badge/badge-overview/badge-overview-example.ts b/src/components-examples/material/badge/badge-overview/badge-overview-example.ts index 5f6615998577..60e646a938de 100644 --- a/src/components-examples/material/badge/badge-overview/badge-overview-example.ts +++ b/src/components-examples/material/badge/badge-overview/badge-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; import {MatBadgeModule} from '@angular/material/badge'; @@ -13,9 +13,9 @@ import {MatBadgeModule} from '@angular/material/badge'; imports: [MatBadgeModule, MatButtonModule, MatIconModule], }) export class BadgeOverviewExample { - hidden = signal(false); + hidden = false; toggleBadgeVisibility() { - this.hidden.update(hidden => !hidden); + this.hidden = !this.hidden; } } diff --git a/src/components-examples/material/bottom-sheet/bottom-sheet-harness/bottom-sheet-harness-example.ts b/src/components-examples/material/bottom-sheet/bottom-sheet-harness/bottom-sheet-harness-example.ts index 6fa7e1051d51..cabf830a79c3 100644 --- a/src/components-examples/material/bottom-sheet/bottom-sheet-harness/bottom-sheet-harness-example.ts +++ b/src/components-examples/material/bottom-sheet/bottom-sheet-harness/bottom-sheet-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, TemplateRef, inject, viewChild} from '@angular/core'; +import {Component, TemplateRef, ViewChild, inject} from '@angular/core'; import { MatBottomSheet, MatBottomSheetConfig, @@ -16,9 +16,9 @@ import { export class BottomSheetHarnessExample { readonly bottomSheet = inject(MatBottomSheet); - template = viewChild.required>(TemplateRef); + @ViewChild(TemplateRef) template!: TemplateRef; open(config?: MatBottomSheetConfig) { - return this.bottomSheet.open(this.template(), config); + return this.bottomSheet.open(this.template, config); } } diff --git a/src/components-examples/material/button-toggle/button-toggle-appearance/button-toggle-appearance-example.ts b/src/components-examples/material/button-toggle/button-toggle-appearance/button-toggle-appearance-example.ts index c36086826ccc..28ff8a34b4d2 100644 --- a/src/components-examples/material/button-toggle/button-toggle-appearance/button-toggle-appearance-example.ts +++ b/src/components-examples/material/button-toggle/button-toggle-appearance/button-toggle-appearance-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; /** @@ -9,5 +9,6 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle'; templateUrl: 'button-toggle-appearance-example.html', styleUrl: 'button-toggle-appearance-example.css', imports: [MatButtonToggleModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonToggleAppearanceExample {} diff --git a/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.html b/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.html index ec55cadec413..1cd05357acc3 100644 --- a/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.html +++ b/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.html @@ -5,7 +5,7 @@

    Button Toggle inside of a Template-driven form

    Italic Underline -

    Chosen value is {{fontStyle()}}

    +

    Chosen value is {{fontStyle}}

    diff --git a/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.ts b/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.ts index 1cf8dfbb669a..21f19dafd0a8 100644 --- a/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.ts +++ b/src/components-examples/material/button-toggle/button-toggle-forms/button-toggle-forms-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; @@ -9,8 +9,9 @@ import {MatButtonToggleModule} from '@angular/material/button-toggle'; selector: 'button-toggle-forms-example', templateUrl: 'button-toggle-forms-example.html', imports: [MatButtonToggleModule, FormsModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonToggleFormsExample { fontStyleControl = new FormControl(''); - fontStyle = signal(undefined); + fontStyle?: string; } diff --git a/src/components-examples/material/button-toggle/button-toggle-harness/button-toggle-harness-example.ts b/src/components-examples/material/button-toggle/button-toggle-harness/button-toggle-harness-example.ts index f9f5a6b1e14c..d7427bca51ee 100644 --- a/src/components-examples/material/button-toggle/button-toggle-harness/button-toggle-harness-example.ts +++ b/src/components-examples/material/button-toggle/button-toggle-harness/button-toggle-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatButtonToggleAppearance, MatButtonToggleModule} from '@angular/material/button-toggle'; /** @@ -8,6 +8,7 @@ import {MatButtonToggleAppearance, MatButtonToggleModule} from '@angular/materia selector: 'button-toggle-harness-example', templateUrl: 'button-toggle-harness-example.html', imports: [MatButtonToggleModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonToggleHarnessExample { disabled = signal(false); diff --git a/src/components-examples/material/button-toggle/button-toggle-mode/button-toggle-mode-example.ts b/src/components-examples/material/button-toggle/button-toggle-mode/button-toggle-mode-example.ts index 31c428b8f228..cdd7170b7f02 100644 --- a/src/components-examples/material/button-toggle/button-toggle-mode/button-toggle-mode-example.ts +++ b/src/components-examples/material/button-toggle/button-toggle-mode/button-toggle-mode-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -9,6 +9,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; selector: 'button-toggle-mode-example', templateUrl: 'button-toggle-mode-example.html', imports: [MatButtonToggleModule, MatCheckboxModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonToggleModeExample { hideSingleSelectionIndicator = signal(false); diff --git a/src/components-examples/material/button/BUILD.bazel b/src/components-examples/material/button/BUILD.bazel index 29180a79587e..04468715abbc 100644 --- a/src/components-examples/material/button/BUILD.bazel +++ b/src/components-examples/material/button/BUILD.bazel @@ -20,10 +20,8 @@ ng_project( "//src/cdk/testing/testbed", "//src/material/button", "//src/material/button/testing", - "//src/material/checkbox", "//src/material/divider", "//src/material/icon", - "//src/material/progress-spinner", "//src/material/tooltip", ], ) diff --git a/src/components-examples/material/button/button-harness/button-harness-example.html b/src/components-examples/material/button/button-harness/button-harness-example.html index 3bdbeeeab016..f40c06ea8d5e 100644 --- a/src/components-examples/material/button/button-harness/button-harness-example.html +++ b/src/components-examples/material/button/button-harness/button-harness-example.html @@ -1,3 +1,3 @@ - diff --git a/src/components-examples/material/button/button-harness/button-harness-example.spec.ts b/src/components-examples/material/button/button-harness/button-harness-example.spec.ts index 6cd06137e581..3d8c4e7cdd4f 100644 --- a/src/components-examples/material/button/button-harness/button-harness-example.spec.ts +++ b/src/components-examples/material/button/button-harness/button-harness-example.spec.ts @@ -27,8 +27,8 @@ describe('ButtonHarnessExample', () => { it('should click a button', async () => { const button = await loader.getHarness(MatButtonHarness.with({text: 'Basic button'})); - expect(fixture.componentInstance.clicked()).toBe(false); + expect(fixture.componentInstance.clicked).toBe(false); await button.click(); - expect(fixture.componentInstance.clicked()).toBe(true); + expect(fixture.componentInstance.clicked).toBe(true); }); }); diff --git a/src/components-examples/material/button/button-harness/button-harness-example.ts b/src/components-examples/material/button/button-harness/button-harness-example.ts index d7db3a774930..cd6c4ff89cfa 100644 --- a/src/components-examples/material/button/button-harness/button-harness-example.ts +++ b/src/components-examples/material/button/button-harness/button-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; /** @@ -10,5 +10,5 @@ import {MatButtonModule} from '@angular/material/button'; imports: [MatButtonModule], }) export class ButtonHarnessExample { - clicked = signal(false); + clicked = false; } diff --git a/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.html b/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.html deleted file mode 100644 index 941e54032140..000000000000 --- a/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -

    Button with a MatProgressSpinner

    - -
    -
    -

    Button with a custom progress indicator

    - -
    \ No newline at end of file diff --git a/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.ts b/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.ts deleted file mode 100644 index ea3ec08cc1ee..000000000000 --- a/src/components-examples/material/button/button-progress-indicator/button-progress-indicator-example.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Component, signal} from '@angular/core'; -import {MatButton} from '@angular/material/button'; -import {MatProgressSpinner} from '@angular/material/progress-spinner'; - -/** - * @title Buttons with progress indicators - */ -@Component({ - selector: 'button-progress-indicator-example', - templateUrl: 'button-progress-indicator-example.html', - imports: [MatButton, MatProgressSpinner], -}) -export class ButtonProgressIndicatorExample { - protected readonly showProgress = signal(false); - - protected toggleShowProgress() { - this.showProgress.update(show => !show); - } -} diff --git a/src/components-examples/material/button/index.ts b/src/components-examples/material/button/index.ts index e60749acc079..4557e8edb62e 100644 --- a/src/components-examples/material/button/index.ts +++ b/src/components-examples/material/button/index.ts @@ -1,4 +1,3 @@ export {ButtonOverviewExample} from './button-overview/button-overview-example'; export {ButtonDisabledInteractiveExample} from './button-disabled-interactive/button-disabled-interactive-example'; export {ButtonHarnessExample} from './button-harness/button-harness-example'; -export {ButtonProgressIndicatorExample} from './button-progress-indicator/button-progress-indicator-example'; diff --git a/src/components-examples/material/card/card-actions/card-actions-example.ts b/src/components-examples/material/card/card-actions/card-actions-example.ts index dad31b6a9a74..adba3cd18634 100644 --- a/src/components-examples/material/card/card-actions/card-actions-example.ts +++ b/src/components-examples/material/card/card-actions/card-actions-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -9,5 +9,6 @@ import {MatCardModule} from '@angular/material/card'; selector: 'card-actions-example', templateUrl: 'card-actions-example.html', imports: [MatCardModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardActionsExample {} diff --git a/src/components-examples/material/card/card-footer/card-footer-example.ts b/src/components-examples/material/card/card-footer/card-footer-example.ts index bfe6abbd86da..bef1531cdd06 100644 --- a/src/components-examples/material/card/card-footer/card-footer-example.ts +++ b/src/components-examples/material/card/card-footer/card-footer-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatProgressBarModule} from '@angular/material/progress-bar'; import {MatCardModule} from '@angular/material/card'; import {MatChipsModule} from '@angular/material/chips'; @@ -11,6 +11,7 @@ import {MatChipsModule} from '@angular/material/chips'; templateUrl: 'card-footer-example.html', styleUrl: 'card-footer-example.css', imports: [MatCardModule, MatChipsModule, MatProgressBarModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardFooterExample { longText = `The Chihuahua is a Mexican breed of toy dog. It is named for the diff --git a/src/components-examples/material/card/card-harness/card-harness-example.ts b/src/components-examples/material/card/card-harness/card-harness-example.ts index 4d52796a3fd7..80aa466608a1 100644 --- a/src/components-examples/material/card/card-harness/card-harness-example.ts +++ b/src/components-examples/material/card/card-harness/card-harness-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -9,5 +9,6 @@ import {MatCardModule} from '@angular/material/card'; selector: 'card-harness-example', templateUrl: 'card-harness-example.html', imports: [MatCardModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardHarnessExample {} diff --git a/src/components-examples/material/card/card-media-size/card-media-size-example.ts b/src/components-examples/material/card/card-media-size/card-media-size-example.ts index 5af21122e018..335ba0243c0f 100644 --- a/src/components-examples/material/card/card-media-size/card-media-size-example.ts +++ b/src/components-examples/material/card/card-media-size/card-media-size-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatCardModule} from '@angular/material/card'; /** @@ -9,6 +9,7 @@ import {MatCardModule} from '@angular/material/card'; templateUrl: 'card-media-size-example.html', styleUrl: 'card-media-size-example.css', imports: [MatCardModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardMediaSizeExample { longText = `The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog diff --git a/src/components-examples/material/card/card-overview/card-overview-example.ts b/src/components-examples/material/card/card-overview/card-overview-example.ts index 99a24f0f464f..59b143aad555 100644 --- a/src/components-examples/material/card/card-overview/card-overview-example.ts +++ b/src/components-examples/material/card/card-overview/card-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -10,5 +10,6 @@ import {MatCardModule} from '@angular/material/card'; templateUrl: 'card-overview-example.html', styleUrl: 'card-overview-example.css', imports: [MatCardModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardOverviewExample {} diff --git a/src/components-examples/material/checkbox/checkbox-configurable/checkbox-configurable-example.ts b/src/components-examples/material/checkbox/checkbox-configurable/checkbox-configurable-example.ts index 0fd29f54fa90..a22f8f980db5 100644 --- a/src/components-examples/material/checkbox/checkbox-configurable/checkbox-configurable-example.ts +++ b/src/components-examples/material/checkbox/checkbox-configurable/checkbox-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, model} from '@angular/core'; +import {ChangeDetectionStrategy, Component, model} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatCardModule} from '@angular/material/card'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -12,6 +12,7 @@ import {MatRadioModule} from '@angular/material/radio'; templateUrl: 'checkbox-configurable-example.html', styleUrl: 'checkbox-configurable-example.css', imports: [MatCardModule, MatCheckboxModule, FormsModule, MatRadioModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckboxConfigurableExample { readonly checked = model(false); diff --git a/src/components-examples/material/checkbox/checkbox-harness/checkbox-harness-example.ts b/src/components-examples/material/checkbox/checkbox-harness/checkbox-harness-example.ts index fa4d834d0e56..c33f28bfaaa7 100644 --- a/src/components-examples/material/checkbox/checkbox-harness/checkbox-harness-example.ts +++ b/src/components-examples/material/checkbox/checkbox-harness/checkbox-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input} from '@angular/core'; import {MatCheckboxModule} from '@angular/material/checkbox'; /** @@ -8,6 +8,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; selector: 'checkbox-harness-example', templateUrl: 'checkbox-harness-example.html', imports: [MatCheckboxModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckboxHarnessExample { readonly disabled = input(true); diff --git a/src/components-examples/material/checkbox/checkbox-overview/checkbox-overview-example.ts b/src/components-examples/material/checkbox/checkbox-overview/checkbox-overview-example.ts index fcb1a085091b..1f0d05fe5e3a 100644 --- a/src/components-examples/material/checkbox/checkbox-overview/checkbox-overview-example.ts +++ b/src/components-examples/material/checkbox/checkbox-overview/checkbox-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, computed, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -16,6 +16,7 @@ export interface Task { templateUrl: 'checkbox-overview-example.html', styleUrl: 'checkbox-overview-example.css', imports: [MatCheckboxModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckboxOverviewExample { readonly task = signal({ diff --git a/src/components-examples/material/checkbox/checkbox-reactive-forms/checkbox-reactive-forms-example.ts b/src/components-examples/material/checkbox/checkbox-reactive-forms/checkbox-reactive-forms-example.ts index 26071f338bd7..ad0f0fe7aaf6 100644 --- a/src/components-examples/material/checkbox/checkbox-reactive-forms/checkbox-reactive-forms-example.ts +++ b/src/components-examples/material/checkbox/checkbox-reactive-forms/checkbox-reactive-forms-example.ts @@ -1,5 +1,5 @@ import {JsonPipe} from '@angular/common'; -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {FormBuilder, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -9,6 +9,7 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; templateUrl: 'checkbox-reactive-forms-example.html', styleUrl: 'checkbox-reactive-forms-example.css', imports: [FormsModule, ReactiveFormsModule, MatCheckboxModule, JsonPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckboxReactiveFormsExample { private readonly _formBuilder = inject(FormBuilder); diff --git a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts index 637e520ccfe5..8ca45828cfc7 100644 --- a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts +++ b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts @@ -1,6 +1,6 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; import {COMMA, ENTER} from '@angular/cdk/keycodes'; -import {Component, computed, inject, model, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, computed, inject, model, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import { MatAutocompleteModule, @@ -20,6 +20,7 @@ import {MatIconModule} from '@angular/material/icon'; // Make sure to import `MatAutocompleteModule` before `MatChipsModule` to prevent adding typed // text when autocomplete option is selected via keyboard). imports: [MatFormFieldModule, MatAutocompleteModule, MatChipsModule, MatIconModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsAutocompleteExample { readonly separatorKeysCodes: number[] = [ENTER, COMMA]; diff --git a/src/components-examples/material/chips/chips-avatar/chips-avatar-example.ts b/src/components-examples/material/chips/chips-avatar/chips-avatar-example.ts index 08a9001389aa..de7ce8249368 100644 --- a/src/components-examples/material/chips/chips-avatar/chips-avatar-example.ts +++ b/src/components-examples/material/chips/chips-avatar/chips-avatar-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatChipsModule} from '@angular/material/chips'; /** @@ -10,5 +10,6 @@ import {MatChipsModule} from '@angular/material/chips'; templateUrl: 'chips-avatar-example.html', styleUrl: 'chips-avatar-example.css', imports: [MatChipsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsAvatarExample {} diff --git a/src/components-examples/material/chips/chips-drag-drop/chips-drag-drop-example.ts b/src/components-examples/material/chips/chips-drag-drop/chips-drag-drop-example.ts index c66506472e8f..a5ef143bc600 100644 --- a/src/components-examples/material/chips/chips-drag-drop/chips-drag-drop-example.ts +++ b/src/components-examples/material/chips/chips-drag-drop/chips-drag-drop-example.ts @@ -1,5 +1,5 @@ import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop'; -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatChipsModule} from '@angular/material/chips'; export interface Vegetable { @@ -14,6 +14,7 @@ export interface Vegetable { templateUrl: 'chips-drag-drop-example.html', styleUrl: 'chips-drag-drop-example.css', imports: [MatChipsModule, CdkDropList, CdkDrag], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsDragDropExample { readonly vegetables = signal([ diff --git a/src/components-examples/material/chips/chips-form-control/chips-form-control-example.ts b/src/components-examples/material/chips/chips-form-control/chips-form-control-example.ts index c908e47407b1..1d204ab35d2f 100644 --- a/src/components-examples/material/chips/chips-form-control/chips-form-control-example.ts +++ b/src/components-examples/material/chips/chips-form-control/chips-form-control-example.ts @@ -1,5 +1,5 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; -import {Component, inject, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips'; @@ -21,6 +21,7 @@ import {MatIconModule} from '@angular/material/icon'; ReactiveFormsModule, MatIconModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsFormControlExample { readonly keywords = signal(['angular', 'how-to', 'tutorial', 'accessibility']); diff --git a/src/components-examples/material/chips/chips-harness/chips-harness-example.ts b/src/components-examples/material/chips/chips-harness/chips-harness-example.ts index 2fe35811f806..e4fa273dde13 100644 --- a/src/components-examples/material/chips/chips-harness/chips-harness-example.ts +++ b/src/components-examples/material/chips/chips-harness/chips-harness-example.ts @@ -1,6 +1,6 @@ /// -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatChipsModule} from '@angular/material/chips'; import {MatIconModule} from '@angular/material/icon'; @@ -11,6 +11,7 @@ import {MatIconModule} from '@angular/material/icon'; selector: 'chips-harness-example', templateUrl: 'chips-harness-example.html', imports: [MatChipsModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsHarnessExample { isDisabled = signal(false); diff --git a/src/components-examples/material/chips/chips-input/chips-input-example.ts b/src/components-examples/material/chips/chips-input/chips-input-example.ts index 45937d0b2004..75e5c9cc6801 100644 --- a/src/components-examples/material/chips/chips-input/chips-input-example.ts +++ b/src/components-examples/material/chips/chips-input/chips-input-example.ts @@ -1,6 +1,6 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; import {COMMA, ENTER} from '@angular/cdk/keycodes'; -import {Component, inject, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; import {MatChipEditedEvent, MatChipInputEvent, MatChipsModule} from '@angular/material/chips'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; @@ -17,6 +17,7 @@ export interface Fruit { templateUrl: 'chips-input-example.html', styleUrl: 'chips-input-example.css', imports: [MatFormFieldModule, MatChipsModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsInputExample { readonly addOnBlur = true; diff --git a/src/components-examples/material/chips/chips-overview/chips-overview-example.ts b/src/components-examples/material/chips/chips-overview/chips-overview-example.ts index 4d8e8fa08c54..1aa611fc93b6 100644 --- a/src/components-examples/material/chips/chips-overview/chips-overview-example.ts +++ b/src/components-examples/material/chips/chips-overview/chips-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatChipsModule} from '@angular/material/chips'; /** @@ -8,5 +8,6 @@ import {MatChipsModule} from '@angular/material/chips'; selector: 'chips-overview-example', templateUrl: 'chips-overview-example.html', imports: [MatChipsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsOverviewExample {} diff --git a/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.html b/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.html index 7b9b699e0f1b..57d9876d271d 100644 --- a/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.html +++ b/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.html @@ -3,8 +3,8 @@

    Chips inside of a Reactive form

    Video keywords - @for (keyword of formControl.value; track keyword) { - + @for (keyword of reactiveKeywords(); track keyword) { + {{keyword}}
    diff --git a/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.ts b/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.ts index e5024d96cca3..3f6125a926e0 100644 --- a/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.ts +++ b/src/components-examples/material/chips/chips-reactive-form/chips-reactive-form-example.ts @@ -1,5 +1,5 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatChipInputEvent, MatChipsModule} from '@angular/material/chips'; @@ -20,33 +20,37 @@ import {MatIconModule} from '@angular/material/icon'; ReactiveFormsModule, MatIconModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsReactiveFormExample { - private _announcer = inject(LiveAnnouncer); + readonly reactiveKeywords = signal(['angular', 'how-to', 'tutorial', 'accessibility']); + readonly formControl = new FormControl(['angular']); - readonly formControl = new FormControl(['angular', 'how-to', 'tutorial', 'accessibility'], { - nonNullable: true, - }); + announcer = inject(LiveAnnouncer); - addKeyword(event: MatChipInputEvent): void { - const value = (event.value || '').trim(); + removeReactiveKeyword(keyword: string) { + this.reactiveKeywords.update(keywords => { + const index = keywords.indexOf(keyword); + if (index < 0) { + return keywords; + } - if (value) { - this.formControl.setValue([...this.formControl.value, value]); - this._announcer.announce(`added ${value} to reactive form`); - } - - event.chipInput.clear(); + keywords.splice(index, 1); + this.announcer.announce(`removed ${keyword} from reactive form`); + return [...keywords]; + }); } - removeKeyword(keyword: string) { - const keywords = this.formControl.value; - const index = keywords.lastIndexOf(keyword); + addReactiveKeyword(event: MatChipInputEvent): void { + const value = (event.value || '').trim(); - if (index > -1) { - keywords.splice(index, 1); - this.formControl.setValue([...keywords]); - this._announcer.announce(`removed ${keyword} from reactive form`); + // Add our keyword + if (value) { + this.reactiveKeywords.update(keywords => [...keywords, value]); + this.announcer.announce(`added ${value} to reactive form`); } + + // Clear the input value + event.chipInput!.clear(); } } diff --git a/src/components-examples/material/chips/chips-template-form/chips-template-form-example.html b/src/components-examples/material/chips/chips-template-form/chips-template-form-example.html index 1feb99194dfc..46dd40bdeb30 100644 --- a/src/components-examples/material/chips/chips-template-form/chips-template-form-example.html +++ b/src/components-examples/material/chips/chips-template-form/chips-template-form-example.html @@ -2,9 +2,9 @@

    Chips inside of a Template-driven form

    Video keywords - - @for (keyword of keywords(); track keyword) { - + + @for (keyword of templateKeywords(); track keyword) { + {{keyword}} + diff --git a/src/components-examples/material/core/elevation-overview/elevation-overview-example.ts b/src/components-examples/material/core/elevation-overview/elevation-overview-example.ts index e3f977894e82..19747bab20a6 100644 --- a/src/components-examples/material/core/elevation-overview/elevation-overview-example.ts +++ b/src/components-examples/material/core/elevation-overview/elevation-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; /** @@ -11,5 +11,5 @@ import {MatButtonModule} from '@angular/material/button'; imports: [MatButtonModule], }) export class ElevationOverviewExample { - isActive = signal(false); + isActive = false; } diff --git a/src/components-examples/material/core/ripple-overview/ripple-overview-example.html b/src/components-examples/material/core/ripple-overview/ripple-overview-example.html index c2bb2633c391..7c42bd23f8ef 100644 --- a/src/components-examples/material/core/ripple-overview/ripple-overview-example.html +++ b/src/components-examples/material/core/ripple-overview/ripple-overview-example.html @@ -11,12 +11,13 @@ -
    + [matRippleCentered]="centered" + [matRippleDisabled]="disabled" + [matRippleUnbounded]="unbounded" + [matRippleRadius]="radius" + [matRippleColor]="color"> Click me
    diff --git a/src/components-examples/material/core/ripple-overview/ripple-overview-example.ts b/src/components-examples/material/core/ripple-overview/ripple-overview-example.ts index 0861f9eb240a..1c7e0497a021 100644 --- a/src/components-examples/material/core/ripple-overview/ripple-overview-example.ts +++ b/src/components-examples/material/core/ripple-overview/ripple-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatRippleModule} from '@angular/material/core'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -15,10 +15,10 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; imports: [MatCheckboxModule, FormsModule, MatFormFieldModule, MatInputModule, MatRippleModule], }) export class RippleOverviewExample { - centered = signal(false); - disabled = signal(false); - unbounded = signal(false); + centered = false; + disabled = false; + unbounded = false; - radius = signal(0); - color = signal(''); + radius!: number; + color!: string; } diff --git a/src/components-examples/material/datepicker/date-range-picker-comparison/date-range-picker-comparison-example.ts b/src/components-examples/material/datepicker/date-range-picker-comparison/date-range-picker-comparison-example.ts index 926008117101..13de181754ee 100644 --- a/src/components-examples/material/datepicker/date-range-picker-comparison/date-range-picker-comparison-example.ts +++ b/src/components-examples/material/datepicker/date-range-picker-comparison/date-range-picker-comparison-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -15,6 +15,7 @@ const year = today.getFullYear(); styleUrl: 'date-range-picker-comparison-example.css', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatDatepickerModule, FormsModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DateRangePickerComparisonExample { readonly campaignOne = new FormGroup({ diff --git a/src/components-examples/material/datepicker/date-range-picker-forms/date-range-picker-forms-example.ts b/src/components-examples/material/datepicker/date-range-picker-forms/date-range-picker-forms-example.ts index 87d6054a4fa1..ca9f523d190c 100644 --- a/src/components-examples/material/datepicker/date-range-picker-forms/date-range-picker-forms-example.ts +++ b/src/components-examples/material/datepicker/date-range-picker-forms/date-range-picker-forms-example.ts @@ -1,5 +1,5 @@ import {JsonPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -11,6 +11,7 @@ import {MatFormFieldModule} from '@angular/material/form-field'; templateUrl: 'date-range-picker-forms-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatDatepickerModule, FormsModule, ReactiveFormsModule, JsonPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DateRangePickerFormsExample { readonly range = new FormGroup({ diff --git a/src/components-examples/material/datepicker/date-range-picker-overview/date-range-picker-overview-example.ts b/src/components-examples/material/datepicker/date-range-picker-overview/date-range-picker-overview-example.ts index 734a75ccf605..e6cf8c4594fd 100644 --- a/src/components-examples/material/datepicker/date-range-picker-overview/date-range-picker-overview-example.ts +++ b/src/components-examples/material/datepicker/date-range-picker-overview/date-range-picker-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -9,5 +9,6 @@ import {MatFormFieldModule} from '@angular/material/form-field'; templateUrl: 'date-range-picker-overview-example.html', imports: [MatFormFieldModule, MatDatepickerModule], providers: [provideNativeDateAdapter()], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DateRangePickerOverviewExample {} diff --git a/src/components-examples/material/datepicker/date-range-picker-selection-strategy/date-range-picker-selection-strategy-example.ts b/src/components-examples/material/datepicker/date-range-picker-selection-strategy/date-range-picker-selection-strategy-example.ts index 1fe786dd42e2..e8f4f3c4f7cb 100644 --- a/src/components-examples/material/datepicker/date-range-picker-selection-strategy/date-range-picker-selection-strategy-example.ts +++ b/src/components-examples/material/datepicker/date-range-picker-selection-strategy/date-range-picker-selection-strategy-example.ts @@ -1,4 +1,4 @@ -import {Component, Service, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Injectable, inject} from '@angular/core'; import {DateAdapter, provideNativeDateAdapter} from '@angular/material/core'; import { DateRange, @@ -8,7 +8,7 @@ import { } from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; -@Service({autoProvided: false}) +@Injectable() export class FiveDayRangeSelectionStrategy implements MatDateRangeSelectionStrategy { private _dateAdapter = inject>(DateAdapter); @@ -43,5 +43,6 @@ export class FiveDayRangeSelectionStrategy implements MatDateRangeSelectionSt provideNativeDateAdapter(), ], imports: [MatFormFieldModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DateRangePickerSelectionStrategyExample {} diff --git a/src/components-examples/material/datepicker/datepicker-actions/datepicker-actions-example.ts b/src/components-examples/material/datepicker/datepicker-actions/datepicker-actions-example.ts index 9ab0c858aa12..8e6f395cf7d5 100644 --- a/src/components-examples/material/datepicker/datepicker-actions/datepicker-actions-example.ts +++ b/src/components-examples/material/datepicker/datepicker-actions/datepicker-actions-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatInputModule} from '@angular/material/input'; @@ -12,5 +12,6 @@ import {provideNativeDateAdapter} from '@angular/material/core'; styleUrl: 'datepicker-actions-example.css', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerActionsExample {} diff --git a/src/components-examples/material/datepicker/datepicker-api/datepicker-api-example.ts b/src/components-examples/material/datepicker/datepicker-api/datepicker-api-example.ts index 857466da82de..c10af85bbd57 100644 --- a/src/components-examples/material/datepicker/datepicker-api/datepicker-api-example.ts +++ b/src/components-examples/material/datepicker/datepicker-api/datepicker-api-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -12,5 +12,6 @@ import {MatInputModule} from '@angular/material/input'; styleUrl: 'datepicker-api-example.css', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerApiExample {} diff --git a/src/components-examples/material/datepicker/datepicker-custom-header/datepicker-custom-header-example.ts b/src/components-examples/material/datepicker/datepicker-custom-header/datepicker-custom-header-example.ts index c1a9aa0e3970..f894673e9e2c 100644 --- a/src/components-examples/material/datepicker/datepicker-custom-header/datepicker-custom-header-example.ts +++ b/src/components-examples/material/datepicker/datepicker-custom-header/datepicker-custom-header-example.ts @@ -1,4 +1,4 @@ -import {Component, OnDestroy, signal, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, OnDestroy, signal, inject} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {DateAdapter, MAT_DATE_FORMATS, provideNativeDateAdapter} from '@angular/material/core'; import {MatCalendar, MatDatepickerModule} from '@angular/material/datepicker'; @@ -14,6 +14,7 @@ import {startWith, takeUntil} from 'rxjs/operators'; templateUrl: 'datepicker-custom-header-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerCustomHeaderExample { readonly exampleHeader = ExampleHeader; @@ -54,6 +55,7 @@ export class DatepickerCustomHeaderExample {
    `, imports: [MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExampleHeader implements OnDestroy { private _calendar = inject>(MatCalendar); diff --git a/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.ts b/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.ts index 182c43644dd6..867c4c79d7f6 100644 --- a/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.ts +++ b/src/components-examples/material/datepicker/datepicker-custom-icon/datepicker-custom-icon-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -11,5 +11,6 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-custom-icon-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerCustomIconExample {} diff --git a/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.ts b/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.ts index 8378c9839f27..860860c0b435 100644 --- a/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.ts +++ b/src/components-examples/material/datepicker/datepicker-date-class/datepicker-date-class-example.ts @@ -1,4 +1,4 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatCalendarCellClassFunction, MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -12,6 +12,7 @@ import {MatInputModule} from '@angular/material/input'; encapsulation: ViewEncapsulation.None, providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerDateClassExample { dateClass: MatCalendarCellClassFunction = (cellDate, view) => { diff --git a/src/components-examples/material/datepicker/datepicker-dialog/datepicker-dialog-example.ts b/src/components-examples/material/datepicker/datepicker-dialog/datepicker-dialog-example.ts index 4fff2d3b706f..481569cc28cf 100644 --- a/src/components-examples/material/datepicker/datepicker-dialog/datepicker-dialog-example.ts +++ b/src/components-examples/material/datepicker/datepicker-dialog/datepicker-dialog-example.ts @@ -1,4 +1,4 @@ -import {Component, model, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, model, inject} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import { @@ -20,6 +20,7 @@ export interface DialogData { selector: 'datepicker-dialog-example', templateUrl: 'datepicker-dialog-example.html', imports: [MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerDialogExample { dialog = inject(MatDialog); diff --git a/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.ts b/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.ts index d360279d7517..abd09f240d23 100644 --- a/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.ts +++ b/src/components-examples/material/datepicker/datepicker-disabled/datepicker-disabled-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,5 +10,6 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-disabled-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerDisabledExample {} diff --git a/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.ts b/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.ts index cacf6cb2d60d..f987518bb36d 100644 --- a/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.ts +++ b/src/components-examples/material/datepicker/datepicker-events/datepicker-events-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerInputEvent, MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -11,6 +11,7 @@ import {MatInputModule} from '@angular/material/input'; styleUrl: 'datepicker-events-example.css', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerEventsExample { events = signal([]); diff --git a/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.ts b/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.ts index 49838df93959..5a57d1b7b1e8 100644 --- a/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.ts +++ b/src/components-examples/material/datepicker/datepicker-filter/datepicker-filter-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,6 +10,7 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-filter-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerFilterExample { myFilter = (d: Date | null): boolean => { diff --git a/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.ts b/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.ts index 3d394930ccd8..3041202adb57 100644 --- a/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.ts +++ b/src/components-examples/material/datepicker/datepicker-formats/datepicker-formats-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideLuxonDateAdapter} from '@angular/material-luxon-adapter'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -37,6 +37,7 @@ export const MY_FORMATS = { FormsModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerFormatsExample { readonly date = new FormControl(DateTime.now()); diff --git a/src/components-examples/material/datepicker/datepicker-harness/datepicker-harness-example.ts b/src/components-examples/material/datepicker/datepicker-harness/datepicker-harness-example.ts index 2742e317a3b9..3576cb380b4c 100644 --- a/src/components-examples/material/datepicker/datepicker-harness/datepicker-harness-example.ts +++ b/src/components-examples/material/datepicker/datepicker-harness/datepicker-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, model, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, model, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -12,6 +12,7 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-harness-example.html', providers: [provideNativeDateAdapter()], imports: [MatInputModule, MatDatepickerModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerHarnessExample { date = model(null); diff --git a/src/components-examples/material/datepicker/datepicker-inline-calendar/datepicker-inline-calendar-example.ts b/src/components-examples/material/datepicker/datepicker-inline-calendar/datepicker-inline-calendar-example.ts index 786f86e82a3b..42d7913ab790 100644 --- a/src/components-examples/material/datepicker/datepicker-inline-calendar/datepicker-inline-calendar-example.ts +++ b/src/components-examples/material/datepicker/datepicker-inline-calendar/datepicker-inline-calendar-example.ts @@ -1,4 +1,4 @@ -import {Component, model} from '@angular/core'; +import {ChangeDetectionStrategy, Component, model} from '@angular/core'; import {MatCardModule} from '@angular/material/card'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -10,6 +10,7 @@ import {MatDatepickerModule} from '@angular/material/datepicker'; styleUrl: 'datepicker-inline-calendar-example.css', providers: [provideNativeDateAdapter()], imports: [MatCardModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerInlineCalendarExample { selected = model(null); diff --git a/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.ts b/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.ts index 47086c029cff..39910d4150db 100644 --- a/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.ts +++ b/src/components-examples/material/datepicker/datepicker-locale/datepicker-locale-example.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, computed, inject, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, OnInit, computed, inject, signal} from '@angular/core'; import {provideLuxonDateAdapter} from '@angular/material-luxon-adapter'; import {MatButtonModule} from '@angular/material/button'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; @@ -22,6 +22,7 @@ import {MatInputModule} from '@angular/material/input'; provideLuxonDateAdapter(), ], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerLocaleExample implements OnInit { private readonly _adapter = inject>(DateAdapter); diff --git a/src/components-examples/material/datepicker/datepicker-luxon/datepicker-luxon-example.ts b/src/components-examples/material/datepicker/datepicker-luxon/datepicker-luxon-example.ts index 12f950402bc6..a9b7765afc16 100644 --- a/src/components-examples/material/datepicker/datepicker-luxon/datepicker-luxon-example.ts +++ b/src/components-examples/material/datepicker/datepicker-luxon/datepicker-luxon-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideLuxonDateAdapter} from '@angular/material-luxon-adapter'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -23,6 +23,7 @@ import {DateTime} from 'luxon'; FormsModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerLuxonExample { // Datepicker takes Luxon `DateTime` objects instead of `Date` objects. diff --git a/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.ts b/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.ts index 4225107cce5b..a5f290e96172 100644 --- a/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.ts +++ b/src/components-examples/material/datepicker/datepicker-min-max/datepicker-min-max-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,6 +10,7 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-min-max-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerMinMaxExample { // Set the minimum to January 1st 20 years in the past and December 31st a year in the future. diff --git a/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.ts b/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.ts index b8c5e5aab853..0cca22960dce 100644 --- a/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.ts +++ b/src/components-examples/material/datepicker/datepicker-overview/datepicker-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,5 +10,6 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'datepicker-overview-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerOverviewExample {} diff --git a/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.ts b/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.ts index 5464e970eb0b..701dec0be53b 100644 --- a/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.ts +++ b/src/components-examples/material/datepicker/datepicker-start-view/datepicker-start-view-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,6 +10,7 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'datepicker-start-view-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerStartViewExample { readonly startDate = new Date(1990, 0, 1); diff --git a/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.ts b/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.ts index 4788d482a535..14d2dc5fc149 100644 --- a/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.ts +++ b/src/components-examples/material/datepicker/datepicker-touch/datepicker-touch-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,5 +10,6 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'datepicker-touch-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerTouchExample {} diff --git a/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.ts b/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.ts index 818153cef899..eb093d18ab88 100644 --- a/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.ts +++ b/src/components-examples/material/datepicker/datepicker-value/datepicker-value-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -18,6 +18,7 @@ import {MatInputModule} from '@angular/material/input'; FormsModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerValueExample { readonly date = new FormControl(new Date()); diff --git a/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.ts b/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.ts index 5cf28216db45..f27daf95e608 100644 --- a/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.ts +++ b/src/components-examples/material/datepicker/datepicker-views-selection/datepicker-views-selection-example.ts @@ -1,4 +1,4 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {provideLuxonDateAdapter} from '@angular/material-luxon-adapter'; import {MatDatepicker, MatDatepickerModule} from '@angular/material/datepicker'; @@ -39,6 +39,7 @@ export const MY_FORMATS = { FormsModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DatepickerViewsSelectionExample { readonly date = new FormControl(DateTime.now()); diff --git a/src/components-examples/material/dialog/dialog-animations/dialog-animations-example.ts b/src/components-examples/material/dialog/dialog-animations/dialog-animations-example.ts index 8b8919b4ddeb..3ed75f3f1383 100644 --- a/src/components-examples/material/dialog/dialog-animations/dialog-animations-example.ts +++ b/src/components-examples/material/dialog/dialog-animations/dialog-animations-example.ts @@ -1,4 +1,4 @@ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import { MatDialog, @@ -17,6 +17,7 @@ import { styleUrl: 'dialog-animations-example.css', templateUrl: 'dialog-animations-example.html', imports: [MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogAnimationsExample { readonly dialog = inject(MatDialog); @@ -34,6 +35,7 @@ export class DialogAnimationsExample { selector: 'dialog-animations-example-dialog', templateUrl: 'dialog-animations-example-dialog.html', imports: [MatButtonModule, MatDialogActions, MatDialogClose, MatDialogTitle, MatDialogContent], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogAnimationsExampleDialog { readonly dialogRef = inject(MatDialogRef); diff --git a/src/components-examples/material/dialog/dialog-content/dialog-content-example.ts b/src/components-examples/material/dialog/dialog-content/dialog-content-example.ts index 0ef258cfb6f2..fba38409c9ad 100644 --- a/src/components-examples/material/dialog/dialog-content/dialog-content-example.ts +++ b/src/components-examples/material/dialog/dialog-content/dialog-content-example.ts @@ -1,4 +1,4 @@ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatDialog, MatDialogModule} from '@angular/material/dialog'; @@ -9,6 +9,7 @@ import {MatDialog, MatDialogModule} from '@angular/material/dialog'; selector: 'dialog-content-example', templateUrl: 'dialog-content-example.html', imports: [MatButtonModule, MatDialogModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogContentExample { readonly dialog = inject(MatDialog); @@ -26,5 +27,6 @@ export class DialogContentExample { selector: 'dialog-content-example-dialog', templateUrl: 'dialog-content-example-dialog.html', imports: [MatDialogModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogContentExampleDialog {} diff --git a/src/components-examples/material/dialog/dialog-elements/dialog-elements-example.ts b/src/components-examples/material/dialog/dialog-elements/dialog-elements-example.ts index b076be59f71b..2ebc273d6302 100644 --- a/src/components-examples/material/dialog/dialog-elements/dialog-elements-example.ts +++ b/src/components-examples/material/dialog/dialog-elements/dialog-elements-example.ts @@ -1,4 +1,4 @@ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import { MatDialog, @@ -15,6 +15,7 @@ import { selector: 'dialog-elements-example', templateUrl: 'dialog-elements-example.html', imports: [MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogElementsExample { readonly dialog = inject(MatDialog); @@ -28,5 +29,6 @@ export class DialogElementsExample { selector: 'dialog-elements-example-dialog', templateUrl: 'dialog-elements-example-dialog.html', imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogElementsExampleDialog {} diff --git a/src/components-examples/material/dialog/dialog-from-menu/dialog-from-menu-example.ts b/src/components-examples/material/dialog/dialog-from-menu/dialog-from-menu-example.ts index e4685c9b8541..0af59bda48e3 100644 --- a/src/components-examples/material/dialog/dialog-from-menu/dialog-from-menu-example.ts +++ b/src/components-examples/material/dialog/dialog-from-menu/dialog-from-menu-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, viewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, viewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import { MatDialog, @@ -14,6 +14,7 @@ import {MatMenuModule, MatMenuTrigger} from '@angular/material/menu'; selector: 'dialog-from-menu-example', templateUrl: 'dialog-from-menu-example.html', imports: [MatButtonModule, MatMenuModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogFromMenuExample { readonly menuTrigger = viewChild.required(MatMenuTrigger); @@ -35,5 +36,6 @@ export class DialogFromMenuExample { selector: 'dialog-from-menu-dialog', templateUrl: 'dialog-from-menu-example-dialog.html', imports: [MatDialogContent, MatDialogActions, MatDialogClose, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogFromMenuExampleDialog {} diff --git a/src/components-examples/material/dialog/dialog-harness/dialog-harness-example.ts b/src/components-examples/material/dialog/dialog-harness/dialog-harness-example.ts index 461cd16b67c0..62a37bbd085e 100644 --- a/src/components-examples/material/dialog/dialog-harness/dialog-harness-example.ts +++ b/src/components-examples/material/dialog/dialog-harness/dialog-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, TemplateRef, inject, viewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, TemplateRef, inject, viewChild} from '@angular/core'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; /** @@ -7,6 +7,7 @@ import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; @Component({ selector: 'dialog-harness-example', templateUrl: 'dialog-harness-example.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogHarnessExample { readonly dialogTemplate = viewChild.required(TemplateRef); diff --git a/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.ts b/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.ts index 3e534b89e694..595cbe23c628 100644 --- a/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.ts +++ b/src/components-examples/material/dialog/dialog-overview/dialog-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, model, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, model, signal} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import { @@ -25,6 +25,7 @@ export interface DialogData { selector: 'dialog-overview-example', templateUrl: 'dialog-overview-example.html', imports: [MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogOverviewExample { readonly animal = signal(''); diff --git a/src/components-examples/material/divider/divider-harness/divider-harness-example.ts b/src/components-examples/material/divider/divider-harness/divider-harness-example.ts index beb6f2f8847a..f0304f188e9f 100644 --- a/src/components-examples/material/divider/divider-harness/divider-harness-example.ts +++ b/src/components-examples/material/divider/divider-harness/divider-harness-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatDividerModule} from '@angular/material/divider'; /** @@ -8,5 +8,6 @@ import {MatDividerModule} from '@angular/material/divider'; selector: 'divider-harness-example', templateUrl: 'divider-harness-example.html', imports: [MatDividerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DividerHarnessExample {} diff --git a/src/components-examples/material/divider/divider-overview/divider-overview-example.ts b/src/components-examples/material/divider/divider-overview/divider-overview-example.ts index 23cfdea84076..13aa918efee6 100644 --- a/src/components-examples/material/divider/divider-overview/divider-overview-example.ts +++ b/src/components-examples/material/divider/divider-overview/divider-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatDividerModule} from '@angular/material/divider'; import {MatListModule} from '@angular/material/list'; @@ -9,5 +9,6 @@ import {MatListModule} from '@angular/material/list'; selector: 'divider-overview-example', templateUrl: 'divider-overview-example.html', imports: [MatListModule, MatDividerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DividerOverviewExample {} diff --git a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts index e506ca6fdbff..103d489cd74e 100644 --- a/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts +++ b/src/components-examples/material/expansion/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts @@ -1,4 +1,4 @@ -import {Component, viewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, viewChild} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -23,6 +23,7 @@ import {MatInputModule} from '@angular/material/input'; MatInputModule, MatDatepickerModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExpansionExpandCollapseAllExample { accordion = viewChild.required(MatAccordion); diff --git a/src/components-examples/material/expansion/expansion-harness/expansion-harness-example.ts b/src/components-examples/material/expansion/expansion-harness/expansion-harness-example.ts index 48460d31b67f..2c9a0406e902 100644 --- a/src/components-examples/material/expansion/expansion-harness/expansion-harness-example.ts +++ b/src/components-examples/material/expansion/expansion-harness/expansion-harness-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatExpansionModule} from '@angular/material/expansion'; /** @@ -8,5 +8,6 @@ import {MatExpansionModule} from '@angular/material/expansion'; selector: 'expansion-harness-example', templateUrl: 'expansion-harness-example.html', imports: [MatExpansionModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExpansionHarnessExample {} diff --git a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.ts b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.ts index 3a94dff2985a..9ee743952c45 100644 --- a/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.ts +++ b/src/components-examples/material/expansion/expansion-overview/expansion-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatExpansionModule} from '@angular/material/expansion'; /** @@ -8,6 +8,7 @@ import {MatExpansionModule} from '@angular/material/expansion'; selector: 'expansion-overview-example', templateUrl: 'expansion-overview-example.html', imports: [MatExpansionModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExpansionOverviewExample { readonly panelOpenState = signal(false); diff --git a/src/components-examples/material/expansion/expansion-steps/expansion-steps-example.ts b/src/components-examples/material/expansion/expansion-steps/expansion-steps-example.ts index 4557bd2e3cdd..38baf06f4d9c 100644 --- a/src/components-examples/material/expansion/expansion-steps/expansion-steps-example.ts +++ b/src/components-examples/material/expansion/expansion-steps/expansion-steps-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatDatepickerModule} from '@angular/material/datepicker'; @@ -23,6 +23,7 @@ import {MatInputModule} from '@angular/material/input'; MatButtonModule, MatDatepickerModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExpansionStepsExample { step = signal(0); diff --git a/src/components-examples/material/form-field/form-field-appearance/form-field-appearance-example.ts b/src/components-examples/material/form-field/form-field-appearance/form-field-appearance-example.ts index af126d98c165..563329356c56 100644 --- a/src/components-examples/material/form-field/form-field-appearance/form-field-appearance-example.ts +++ b/src/components-examples/material/form-field/form-field-appearance/form-field-appearance-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; import {MatInputModule} from '@angular/material/input'; @@ -8,5 +8,6 @@ import {MatInputModule} from '@angular/material/input'; selector: 'form-field-appearance-example', templateUrl: 'form-field-appearance-example.html', imports: [MatFormFieldModule, MatInputModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldAppearanceExample {} diff --git a/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.ts b/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.ts index d68e5c8a4dbc..8d47a9f778d6 100644 --- a/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.ts +++ b/src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.ts @@ -1,6 +1,7 @@ import {FocusMonitor} from '@angular/cdk/a11y'; import {AsyncPipe, JsonPipe} from '@angular/common'; import { + ChangeDetectionStrategy, Component, ElementRef, OnDestroy, @@ -48,6 +49,7 @@ import {Subject} from 'rxjs'; AsyncPipe, JsonPipe, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldCustomControlExample { readonly form = new FormGroup({ @@ -75,6 +77,7 @@ export class MyTel { '[id]': 'id', }, imports: [FormsModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MyTelInput implements ControlValueAccessor, MatFormFieldControl, OnDestroy { static nextId = 0; diff --git a/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts b/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts index bcabef71a184..d9aa7c0317dd 100644 --- a/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts +++ b/src/components-examples/material/form-field/form-field-error/form-field-error-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {FormControl, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -11,6 +11,7 @@ import {merge} from 'rxjs'; templateUrl: 'form-field-error-example.html', styleUrl: 'form-field-error-example.css', imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldErrorExample { readonly email = new FormControl('', [Validators.required, Validators.email]); diff --git a/src/components-examples/material/form-field/form-field-harness/form-field-harness-example.ts b/src/components-examples/material/form-field/form-field-harness/form-field-harness-example.ts index 1b547617383d..b6e9bfee1ee9 100644 --- a/src/components-examples/material/form-field/form-field-harness/form-field-harness-example.ts +++ b/src/components-examples/material/form-field/form-field-harness/form-field-harness-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatInputModule} from '@angular/material/input'; @@ -10,6 +10,7 @@ import {MatInputModule} from '@angular/material/input'; selector: 'form-field-harness-example', templateUrl: 'form-field-harness-example.html', imports: [MatFormFieldModule, MatInputModule, FormsModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldHarnessExample { readonly requiredControl = new FormControl('Initial value', [Validators.required]); diff --git a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.ts b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.ts index 622a5b800024..0aef5c3b59a0 100644 --- a/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.ts +++ b/src/components-examples/material/form-field/form-field-hint/form-field-hint-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; @@ -9,6 +9,7 @@ import {MatSelectModule} from '@angular/material/select'; templateUrl: 'form-field-hint-example.html', styleUrl: 'form-field-hint-example.css', imports: [MatFormFieldModule, MatInputModule, MatSelectModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldHintExample { protected readonly value = signal(''); diff --git a/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts b/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts index 43a9b899fa03..f37bf4d4a0ef 100644 --- a/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts +++ b/src/components-examples/material/form-field/form-field-label/form-field-label-example.ts @@ -1,4 +1,4 @@ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {toSignal} from '@angular/core/rxjs-interop'; import {FormBuilder, FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -24,6 +24,7 @@ import {map} from 'rxjs/operators'; MatSelectModule, MatIconModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldLabelExample { readonly hideRequiredControl = new FormControl(false); diff --git a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.ts b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.ts index d8f8fe0c8190..320d5a3d11e2 100644 --- a/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.ts +++ b/src/components-examples/material/form-field/form-field-overview/form-field-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatSelectModule} from '@angular/material/select'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -9,5 +9,6 @@ import {MatFormFieldModule} from '@angular/material/form-field'; templateUrl: 'form-field-overview-example.html', styleUrl: 'form-field-overview-example.css', imports: [MatFormFieldModule, MatInputModule, MatSelectModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldOverviewExample {} diff --git a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts index b5a5cfac8f0c..d2a539fee9be 100644 --- a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts +++ b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatIconModule} from '@angular/material/icon'; @@ -10,6 +10,7 @@ import {MatInputModule} from '@angular/material/input'; templateUrl: 'form-field-prefix-suffix-example.html', styleUrl: 'form-field-prefix-suffix-example.css', imports: [MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FormFieldPrefixSuffixExample { hide = signal(true); diff --git a/src/components-examples/material/icon/icon-harness/icon-harness-example.ts b/src/components-examples/material/icon/icon-harness/icon-harness-example.ts index d05497a79897..7c3a66c15c48 100644 --- a/src/components-examples/material/icon/icon-harness/icon-harness-example.ts +++ b/src/components-examples/material/icon/icon-harness/icon-harness-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; /** @@ -8,5 +8,6 @@ import {MatIconModule} from '@angular/material/icon'; selector: 'icon-harness-example', templateUrl: 'icon-harness-example.html', imports: [MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IconHarnessExample {} diff --git a/src/components-examples/material/icon/icon-overview/icon-overview-example.ts b/src/components-examples/material/icon/icon-overview/icon-overview-example.ts index 6673b97f9e82..025a876641e7 100644 --- a/src/components-examples/material/icon/icon-overview/icon-overview-example.ts +++ b/src/components-examples/material/icon/icon-overview/icon-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; /** @@ -8,5 +8,6 @@ import {MatIconModule} from '@angular/material/icon'; selector: 'icon-overview-example', templateUrl: 'icon-overview-example.html', imports: [MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IconOverviewExample {} diff --git a/src/components-examples/material/icon/icon-svg/icon-svg-example.ts b/src/components-examples/material/icon/icon-svg/icon-svg-example.ts index 4bfcdaded348..68e5b7868394 100644 --- a/src/components-examples/material/icon/icon-svg/icon-svg-example.ts +++ b/src/components-examples/material/icon/icon-svg/icon-svg-example.ts @@ -1,4 +1,4 @@ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {DomSanitizer} from '@angular/platform-browser'; import {MatIconRegistry, MatIconModule} from '@angular/material/icon'; @@ -19,6 +19,7 @@ const THUMBUP_ICON = selector: 'icon-svg-example', templateUrl: 'icon-svg-example.html', imports: [MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IconSvgExample { constructor() { diff --git a/src/components-examples/material/input/input-clearable/input-clearable-example.html b/src/components-examples/material/input/input-clearable/input-clearable-example.html index 733b56c0d057..47c78599d1f9 100644 --- a/src/components-examples/material/input/input-clearable/input-clearable-example.html +++ b/src/components-examples/material/input/input-clearable/input-clearable-example.html @@ -1,8 +1,8 @@ Clearable input - @if (value()) { - } diff --git a/src/components-examples/material/input/input-clearable/input-clearable-example.ts b/src/components-examples/material/input/input-clearable/input-clearable-example.ts index ac199b178877..ecfd4a84a940 100644 --- a/src/components-examples/material/input/input-clearable/input-clearable-example.ts +++ b/src/components-examples/material/input/input-clearable/input-clearable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; import {FormsModule} from '@angular/forms'; @@ -15,5 +15,5 @@ import {MatFormFieldModule} from '@angular/material/form-field'; imports: [MatFormFieldModule, MatInputModule, FormsModule, MatButtonModule, MatIconModule], }) export class InputClearableExample { - value = signal('Clear me'); + value = 'Clear me'; } diff --git a/src/components-examples/material/list/list-navigation/list-navigation-example.html b/src/components-examples/material/list/list-navigation/list-navigation-example.html index cc56e11bbfb3..f371e070827d 100644 --- a/src/components-examples/material/list/list-navigation/list-navigation-example.html +++ b/src/components-examples/material/list/list-navigation/list-navigation-example.html @@ -3,8 +3,8 @@ {{link | titlecase}} } @@ -13,9 +13,9 @@

    List with icons

    @for (link of fragments; track link) { folder {{ link | titlecase}} diff --git a/src/components-examples/material/list/list-navigation/list-navigation-example.ts b/src/components-examples/material/list/list-navigation/list-navigation-example.ts index 1b12f65215f7..3a37f23f192d 100644 --- a/src/components-examples/material/list/list-navigation/list-navigation-example.ts +++ b/src/components-examples/material/list/list-navigation/list-navigation-example.ts @@ -1,5 +1,5 @@ import {TitleCasePipe} from '@angular/common'; -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import {MatListModule} from '@angular/material/list'; @@ -13,5 +13,5 @@ import {MatListModule} from '@angular/material/list'; }) export class ListNavigationExample { fragments = ['inbox', 'outbox', 'drafts']; - activeLink = signal(null); + activeLink: string | null = null; } diff --git a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html index 573093663122..187902602f51 100644 --- a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html +++ b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.html @@ -16,7 +16,7 @@ Page Size Options - @@ -31,17 +31,17 @@
    -
    Output event: {{(pageEvent() | json) || 'No events dispatched yet'}}
    +
    Output event: {{(pageEvent | json) || 'No events dispatched yet'}}
    getNumberOfPages: {{paginator.getNumberOfPages()}}
    diff --git a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts index c568df645c24..850057bf8fa6 100644 --- a/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts +++ b/src/components-examples/material/paginator/paginator-configurable/paginator-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {PageEvent, MatPaginatorModule} from '@angular/material/paginator'; import {JsonPipe} from '@angular/common'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; @@ -23,28 +23,28 @@ import {MatFormFieldModule} from '@angular/material/form-field'; ], }) export class PaginatorConfigurableExample { - length = signal(50); - pageSize = signal(10); - pageIndex = signal(0); - pageSizeOptions = signal([5, 10, 25]); + length = 50; + pageSize = 10; + pageIndex = 0; + pageSizeOptions = [5, 10, 25]; - hidePageSize = signal(false); - showPageSizeOptions = signal(true); - showFirstLastButtons = signal(true); - disabled = signal(false); + hidePageSize = false; + showPageSizeOptions = true; + showFirstLastButtons = true; + disabled = false; - pageEvent = signal(undefined); + pageEvent: PageEvent | undefined; handlePageEvent(e: PageEvent) { - this.pageEvent.set(e); - this.length.set(e.length); - this.pageSize.set(e.pageSize); - this.pageIndex.set(e.pageIndex); + this.pageEvent = e; + this.length = e.length; + this.pageSize = e.pageSize; + this.pageIndex = e.pageIndex; } setPageSizeOptions(setPageSizeOptionsInput: string) { if (setPageSizeOptionsInput) { - this.pageSizeOptions.set(setPageSizeOptionsInput.split(',').map(str => +str)); + this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str); } } } diff --git a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.html b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.html index b8c013a35eb4..0dcd46611ddd 100644 --- a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.html +++ b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.html @@ -1,9 +1,9 @@ diff --git a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.spec.ts b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.spec.ts index 0f9190d34974..aae7ba53f0d5 100644 --- a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.spec.ts +++ b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.spec.ts @@ -24,26 +24,26 @@ describe('PaginatorHarnessExample', () => { it('should be able to navigate between pages', async () => { const paginator = await loader.getHarness(MatPaginatorHarness); - expect(instance.pageIndex()).toBe(0); + expect(instance.pageIndex).toBe(0); await paginator.goToNextPage(); - expect(instance.pageIndex()).toBe(1); + expect(instance.pageIndex).toBe(1); await paginator.goToPreviousPage(); - expect(instance.pageIndex()).toBe(0); + expect(instance.pageIndex).toBe(0); }); it('should be able to go to the last page', async () => { const paginator = await loader.getHarness(MatPaginatorHarness); - expect(instance.pageIndex()).toBe(0); + expect(instance.pageIndex).toBe(0); await paginator.goToLastPage(); - expect(instance.pageIndex()).toBe(49); + expect(instance.pageIndex).toBe(49); }); it('should be able to set the page size', async () => { const paginator = await loader.getHarness(MatPaginatorHarness); - expect(instance.pageSize()).toBe(10); + expect(instance.pageSize).toBe(10); await paginator.setPageSize(25); - expect(instance.pageSize()).toBe(25); + expect(instance.pageSize).toBe(25); }); }); diff --git a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.ts b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.ts index ee49882013d1..244a4d4bfe96 100644 --- a/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.ts +++ b/src/components-examples/material/paginator/paginator-harness/paginator-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {PageEvent, MatPaginatorModule} from '@angular/material/paginator'; /** @@ -10,15 +10,15 @@ import {PageEvent, MatPaginatorModule} from '@angular/material/paginator'; imports: [MatPaginatorModule], }) export class PaginatorHarnessExample { - length = signal(500); - pageSize = signal(10); - pageIndex = signal(0); - pageSizeOptions = signal([5, 10, 25]); - showFirstLastButtons = signal(true); + length = 500; + pageSize = 10; + pageIndex = 0; + pageSizeOptions = [5, 10, 25]; + showFirstLastButtons = true; handlePageEvent(event: PageEvent) { - this.length.set(event.length); - this.pageSize.set(event.pageSize); - this.pageIndex.set(event.pageIndex); + this.length = event.length; + this.pageSize = event.pageSize; + this.pageIndex = event.pageIndex; } } diff --git a/src/components-examples/material/paginator/paginator-intl/paginator-intl-example.ts b/src/components-examples/material/paginator/paginator-intl/paginator-intl-example.ts index 7111717aaa86..77b23d93b5bc 100644 --- a/src/components-examples/material/paginator/paginator-intl/paginator-intl-example.ts +++ b/src/components-examples/material/paginator/paginator-intl/paginator-intl-example.ts @@ -1,10 +1,10 @@ -import {Component, Service} from '@angular/core'; +import {Component, Injectable} from '@angular/core'; import {MatPaginatorIntl, MatPaginatorModule} from '@angular/material/paginator'; import {Subject} from 'rxjs'; import '@angular/localize/init'; -@Service({autoProvided: false}) +@Injectable() export class MyCustomPaginatorIntl implements MatPaginatorIntl { changes = new Subject(); diff --git a/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.html b/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.html index 6d810faead31..4f5fd3a222d2 100644 --- a/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.html +++ b/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.html @@ -20,7 +20,7 @@

    Progress bar configuration

    - @if (mode() === 'determinate' || mode() === 'buffer') { + @if (mode === 'determinate' || mode === 'buffer') {
    @@ -28,7 +28,7 @@

    Progress bar configuration

    } - @if (mode() === 'buffer') { + @if (mode === 'buffer') {
    @@ -46,9 +46,9 @@

    Result

    + [mode]="mode" + [value]="value" + [bufferValue]="bufferValue">
    diff --git a/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.ts b/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.ts index 914845bf9078..3ad5df155fe2 100644 --- a/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.ts +++ b/src/components-examples/material/progress-bar/progress-bar-configurable/progress-bar-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {ProgressBarMode, MatProgressBarModule} from '@angular/material/progress-bar'; import {MatSliderModule} from '@angular/material/slider'; import {FormsModule} from '@angular/forms'; @@ -15,7 +15,7 @@ import {MatCardModule} from '@angular/material/card'; imports: [MatCardModule, MatRadioModule, FormsModule, MatSliderModule, MatProgressBarModule], }) export class ProgressBarConfigurableExample { - mode = signal('determinate'); - value = signal(50); - bufferValue = signal(75); + mode: ProgressBarMode = 'determinate'; + value = 50; + bufferValue = 75; } diff --git a/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.html b/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.html index 23d92d2b5e16..e8dd1590a870 100644 --- a/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.html +++ b/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.html @@ -14,7 +14,7 @@

    Progress spinner configuration

    - @if (mode() === 'determinate') { + @if (mode === 'determinate') {
    @@ -31,8 +31,8 @@

    Result

    + [mode]="mode" + [value]="value"> diff --git a/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.ts b/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.ts index 8430616b7202..e1369fca7c96 100644 --- a/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.ts +++ b/src/components-examples/material/progress-spinner/progress-spinner-configurable/progress-spinner-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {ProgressSpinnerMode, MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {MatSliderModule} from '@angular/material/slider'; import {FormsModule} from '@angular/forms'; @@ -15,6 +15,6 @@ import {MatCardModule} from '@angular/material/card'; imports: [MatCardModule, MatRadioModule, FormsModule, MatSliderModule, MatProgressSpinnerModule], }) export class ProgressSpinnerConfigurableExample { - mode = signal('determinate'); - value = signal(50); + mode: ProgressSpinnerMode = 'determinate'; + value = 50; } diff --git a/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.html b/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.html index 825cf93cc5e1..2908217637ae 100644 --- a/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.html +++ b/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.html @@ -7,4 +7,4 @@ {{season}} } -
    Your favorite season is: {{favoriteSeason()}}
    +
    Your favorite season is: {{favoriteSeason}}
    diff --git a/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.ts b/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.ts index acc5d7f29d66..a9afabdfbf70 100644 --- a/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.ts +++ b/src/components-examples/material/radio/radio-ng-model/radio-ng-model-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatRadioModule} from '@angular/material/radio'; @@ -12,6 +12,6 @@ import {MatRadioModule} from '@angular/material/radio'; imports: [MatRadioModule, FormsModule], }) export class RadioNgModelExample { - favoriteSeason = signal(''); + favoriteSeason = ''; seasons: string[] = ['Winter', 'Spring', 'Summer', 'Autumn']; } diff --git a/src/components-examples/material/select/select-form/select-form-example.html b/src/components-examples/material/select/select-form/select-form-example.html index e0abbb2fef07..3b04b2847f74 100644 --- a/src/components-examples/material/select/select-form/select-form-example.html +++ b/src/components-examples/material/select/select-form/select-form-example.html @@ -8,7 +8,7 @@

    mat-select

    } -

    Selected food: {{selectedValue()}}

    +

    Selected food: {{selectedValue}}

    native html select

    Favorite car @@ -19,5 +19,5 @@

    native html select

    }
    -

    Selected car: {{selectedCar()}}

    +

    Selected car: {{selectedCar}}

    diff --git a/src/components-examples/material/select/select-form/select-form-example.ts b/src/components-examples/material/select/select-form/select-form-example.ts index 4adec595ab14..e0463ae5c5bf 100644 --- a/src/components-examples/material/select/select-form/select-form-example.ts +++ b/src/components-examples/material/select/select-form/select-form-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -23,8 +23,8 @@ interface Car { imports: [FormsModule, MatFormFieldModule, MatSelectModule, MatInputModule], }) export class SelectFormExample { - selectedValue = signal(''); - selectedCar = signal(''); + selectedValue!: string; + selectedCar = ''; foods: Food[] = [ {value: 'steak-0', viewValue: 'Steak'}, diff --git a/src/components-examples/material/select/select-initial-value/select-initial-value-example.html b/src/components-examples/material/select/select-initial-value/select-initial-value-example.html index 5ccce048e791..40d617b8346e 100644 --- a/src/components-examples/material/select/select-initial-value/select-initial-value-example.html +++ b/src/components-examples/material/select/select-initial-value/select-initial-value-example.html @@ -8,7 +8,7 @@

    Basic mat-select with initial value

    } -

    You selected: {{selectedFood()}}

    +

    You selected: {{selectedFood}}

    Basic native select with initial value

    @@ -17,8 +17,8 @@

    Basic native select with initial value

    @for (option of cars; track option) { + [selected]="selectedCar === option.value">{{ option.viewValue }} }
    -

    You selected: {{selectedCar()}}

    +

    You selected: {{selectedCar}}

    diff --git a/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts b/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts index dd38366e6ea1..47a781886dcb 100644 --- a/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts +++ b/src/components-examples/material/select/select-initial-value/select-initial-value-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; @@ -33,10 +33,10 @@ export class SelectInitialValueExample { {value: 'chevrolet', viewValue: 'Chevrolet'}, {value: 'dodge', viewValue: 'Dodge'}, ]; - selectedFood = signal(this.foods[2].value); - selectedCar = signal(this.cars[0].value); + selectedFood = this.foods[2].value; + selectedCar = this.cars[0].value; selectCar(event: Event) { - this.selectedCar.set((event.target as HTMLSelectElement).value); + this.selectedCar = (event.target as HTMLSelectElement).value; } } diff --git a/src/components-examples/material/select/select-selectable-null/select-selectable-null-example.ts b/src/components-examples/material/select/select-selectable-null/select-selectable-null-example.ts index 044e62c0c910..a5b29834cae1 100644 --- a/src/components-examples/material/select/select-selectable-null/select-selectable-null-example.ts +++ b/src/components-examples/material/select/select-selectable-null/select-selectable-null-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select'; @@ -11,7 +11,7 @@ import {MatFormFieldModule} from '@angular/material/form-field'; imports: [MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule], }) export class SelectSelectableNullExample { - value = signal(null); + value: number | null = null; options = [ {label: 'None', value: null}, {label: 'One', value: 1}, diff --git a/src/components-examples/material/select/select-value-binding/select-value-binding-example.html b/src/components-examples/material/select/select-value-binding/select-value-binding-example.html index 76c4d7b2c1e3..061c1851e3b5 100644 --- a/src/components-examples/material/select/select-value-binding/select-value-binding-example.html +++ b/src/components-examples/material/select/select-value-binding/select-value-binding-example.html @@ -8,4 +8,4 @@ -

    You selected: {{selected()}}

    +

    You selected: {{selected}}

    diff --git a/src/components-examples/material/select/select-value-binding/select-value-binding-example.ts b/src/components-examples/material/select/select-value-binding/select-value-binding-example.ts index 9c655ce83eea..213e8af2cd79 100644 --- a/src/components-examples/material/select/select-value-binding/select-value-binding-example.ts +++ b/src/components-examples/material/select/select-value-binding/select-value-binding-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatSelectModule} from '@angular/material/select'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -9,5 +9,5 @@ import {MatFormFieldModule} from '@angular/material/form-field'; imports: [MatFormFieldModule, MatSelectModule], }) export class SelectValueBindingExample { - selected = signal('option2'); + selected = 'option2'; } diff --git a/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.html b/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.html index eac75282a880..be269dee82d4 100644 --- a/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.html +++ b/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.html @@ -1,10 +1,10 @@

    Auto-resizing sidenav

    - @if (showFiller()) { + @if (showFiller) {

    Lorem, ipsum dolor sit amet consectetur.

    } -
    diff --git a/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.ts b/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.ts index 88d1aaef105d..20414f9f9034 100644 --- a/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.ts +++ b/src/components-examples/material/sidenav/sidenav-autosize/sidenav-autosize-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatSidenavModule} from '@angular/material/sidenav'; @@ -12,5 +12,5 @@ import {MatSidenavModule} from '@angular/material/sidenav'; imports: [MatSidenavModule, MatButtonModule], }) export class SidenavAutosizeExample { - showFiller = signal(false); + showFiller = false; } diff --git a/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.html b/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.html index b915b5ad4d75..911537f80de2 100644 --- a/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.html +++ b/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.html @@ -7,7 +7,7 @@

    -

    Closed due to: {{reason()}}

    +

    Closed due to: {{reason}}

    } @else { diff --git a/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.ts b/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.ts index cc4a7f19677c..40bbfc421968 100644 --- a/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.ts +++ b/src/components-examples/material/sidenav/sidenav-disable-close/sidenav-disable-close-example.ts @@ -1,4 +1,4 @@ -import {Component, signal, viewChild} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {MatSidenav, MatSidenavModule} from '@angular/material/sidenav'; import {MatButtonModule} from '@angular/material/button'; @@ -10,13 +10,13 @@ import {MatButtonModule} from '@angular/material/button'; imports: [MatSidenavModule, MatButtonModule], }) export class SidenavDisableCloseExample { - sidenav = viewChild.required('sidenav'); + @ViewChild('sidenav') sidenav!: MatSidenav; - reason = signal(''); + reason = ''; close(reason: string) { - this.reason.set(reason); - this.sidenav().close(); + this.reason = reason; + this.sidenav.close(); } shouldRun = /(^|.)(stackblitz|webcontainer).(io|com)$/.test(window.location.host); diff --git a/src/components-examples/material/sidenav/sidenav-mode/sidenav-mode-example.ts b/src/components-examples/material/sidenav/sidenav-mode/sidenav-mode-example.ts index fb4dbbb9cb01..6324aba4e21c 100644 --- a/src/components-examples/material/sidenav/sidenav-mode/sidenav-mode-example.ts +++ b/src/components-examples/material/sidenav/sidenav-mode/sidenav-mode-example.ts @@ -12,6 +12,6 @@ import {MatButtonModule} from '@angular/material/button'; imports: [MatSidenavModule, MatButtonModule, MatRadioModule, FormsModule, ReactiveFormsModule], }) export class SidenavModeExample { - mode = new FormControl('over'); + mode = new FormControl('over' as MatDrawerMode); shouldRun = /(^|.)(stackblitz|webcontainer).(io|com)$/.test(window.location.host); } diff --git a/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.html b/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.html index 0ddde22d2aa0..a9f781c61e00 100644 --- a/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.html +++ b/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.html @@ -1,7 +1,7 @@ @if (shouldRun) { - + Sidenav content @@ -10,7 +10,7 @@

    Events:

    - @for (e of events(); track $index) { + @for (e of events; track e) {
    {{e}}
    }
    diff --git a/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.ts b/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.ts index d06cddaf7d15..f3e6fb1ed432 100644 --- a/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.ts +++ b/src/components-examples/material/sidenav/sidenav-open-close/sidenav-open-close-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {FormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -12,12 +12,8 @@ import {MatSidenavModule} from '@angular/material/sidenav'; imports: [MatSidenavModule, MatCheckboxModule, FormsModule, MatButtonModule], }) export class SidenavOpenCloseExample { - events = signal<('open!' | 'close!')[]>([]); - opened = signal(false); + events: string[] = []; + opened = false; shouldRun = /(^|.)(stackblitz|webcontainer).(io|com)$/.test(window.location.host); - - trackEvent(event: 'open!' | 'close!') { - this.events.update(events => [...events, event]); - } } diff --git a/src/components-examples/material/slide-toggle/index.ts b/src/components-examples/material/slide-toggle/index.ts index 2ed644b732ab..7ea291a6cd6c 100644 --- a/src/components-examples/material/slide-toggle/index.ts +++ b/src/components-examples/material/slide-toggle/index.ts @@ -2,4 +2,3 @@ export {SlideToggleConfigurableExample} from './slide-toggle-configurable/slide- export {SlideToggleFormsExample} from './slide-toggle-forms/slide-toggle-forms-example'; export {SlideToggleOverviewExample} from './slide-toggle-overview/slide-toggle-overview-example'; export {SlideToggleHarnessExample} from './slide-toggle-harness/slide-toggle-harness-example'; -export {SlideToggleFullWidthExample} from './slide-toggle-full-width/slide-toggle-full-width-example'; diff --git a/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.html b/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.html index 3f99518c7eda..50bb04515072 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.html +++ b/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.html @@ -19,8 +19,8 @@

    Result

    + [checked]="checked" + [disabled]="disabled"> Slide me!
    diff --git a/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.ts b/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.ts index 8ef3247dde26..0c035d6412f3 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.ts +++ b/src/components-examples/material/slide-toggle/slide-toggle-configurable/slide-toggle-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {FormsModule} from '@angular/forms'; @@ -15,6 +15,6 @@ import {MatCardModule} from '@angular/material/card'; imports: [MatCardModule, MatRadioModule, FormsModule, MatCheckboxModule, MatSlideToggleModule], }) export class SlideToggleConfigurableExample { - checked = signal(false); - disabled = signal(false); + checked = false; + disabled = false; } diff --git a/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.html b/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.html index f2062b85a187..c1cb6520ec57 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.html +++ b/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.html @@ -1,6 +1,6 @@

    Slide Toggle using a simple NgModel.

    -Slide Toggle Checked: {{isChecked()}} +Slide Toggle Checked: {{isChecked}}

    Slide Toggle inside of a Template-driven form

    diff --git a/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.ts b/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.ts index dc2c9d5aa372..c8a88fa163fc 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.ts +++ b/src/components-examples/material/slide-toggle/slide-toggle-forms/slide-toggle-forms-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {FormBuilder, FormGroup, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; @@ -15,7 +15,7 @@ import {MatSlideToggleModule} from '@angular/material/slide-toggle'; export class SlideToggleFormsExample { private _formBuilder = inject(FormBuilder); - isChecked = signal(true); + isChecked = true; formGroup = this._formBuilder.group({ enableWifi: '', acceptTerms: ['', Validators.requiredTrue], diff --git a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.css b/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.css deleted file mode 100644 index 4ce4ce716a5a..000000000000 --- a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.css +++ /dev/null @@ -1,6 +0,0 @@ -.example-full-width-container { - width: 300px; - border: 1px solid #ccc; - padding: 16px; - border-radius: 4px; -} diff --git a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.html b/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.html deleted file mode 100644 index d26223b3b9d4..000000000000 --- a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.html +++ /dev/null @@ -1,4 +0,0 @@ -
    - Full width slide toggle - Full width slide toggle -
    diff --git a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.ts b/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.ts deleted file mode 100644 index 54102a85ed40..000000000000 --- a/src/components-examples/material/slide-toggle/slide-toggle-full-width/slide-toggle-full-width-example.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {Component} from '@angular/core'; -import {MatSlideToggle} from '@angular/material/slide-toggle'; - -/** - * @title Full-width slide toggle - */ -@Component({ - selector: 'slide-toggle-full-width-example', - templateUrl: 'slide-toggle-full-width-example.html', - styleUrl: 'slide-toggle-full-width-example.css', - imports: [MatSlideToggle], -}) -export class SlideToggleFullWidthExample {} diff --git a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.html b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.html index e13fd3cae20d..a55dee9c7a7a 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.html +++ b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.html @@ -3,6 +3,6 @@ name="first-name"> First - + Second diff --git a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.spec.ts b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.spec.ts index 580a5ba26ea5..6eb602cc5f97 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.spec.ts +++ b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.spec.ts @@ -40,7 +40,8 @@ describe('SlideToggleHarnessExample', () => { }); it('should toggle slide-toggle', async () => { - fixture.componentInstance.disabled.set(false); + fixture.componentInstance.disabled = false; + fixture.changeDetectorRef.markForCheck(); const [checkedToggle, uncheckedToggle] = await loader.getAllHarnesses(MatSlideToggleHarness); await checkedToggle.toggle(); await uncheckedToggle.toggle(); diff --git a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.ts b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.ts index 78355e501858..261bf0acddb7 100644 --- a/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.ts +++ b/src/components-examples/material/slide-toggle/slide-toggle-harness/slide-toggle-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; @@ -11,6 +11,6 @@ import {MatSlideToggleModule} from '@angular/material/slide-toggle'; imports: [MatSlideToggleModule, FormsModule, ReactiveFormsModule], }) export class SlideToggleHarnessExample { - disabled = signal(true); + disabled = true; ctrl = new FormControl(true); } diff --git a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html index b45842a16207..f9c7761bf4f2 100644 --- a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html +++ b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.html @@ -45,12 +45,12 @@

    Result

    + [disabled]="disabled" + [max]="max" + [min]="min" + [step]="step" + [discrete]="thumbLabel" + [showTickMarks]="showTicks"> diff --git a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.ts b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.ts index e846f5a514bc..4d635c817de4 100644 --- a/src/components-examples/material/slider/slider-configurable/slider-configurable-example.ts +++ b/src/components-examples/material/slider/slider-configurable/slider-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatSliderModule} from '@angular/material/slider'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {FormsModule} from '@angular/forms'; @@ -23,11 +23,11 @@ import {MatCardModule} from '@angular/material/card'; ], }) export class SliderConfigurableExample { - disabled = signal(false); - max = signal(100); - min = signal(0); - showTicks = signal(false); - step = signal(1); - thumbLabel = signal(false); - value = signal(0); + disabled = false; + max = 100; + min = 0; + showTicks = false; + step = 1; + thumbLabel = false; + value = 0; } diff --git a/src/components-examples/material/snack-bar/snack-bar-annotated-component/snack-bar-annotated-component-example.ts b/src/components-examples/material/snack-bar/snack-bar-annotated-component/snack-bar-annotated-component-example.ts index f489abef227d..435b1963c74d 100644 --- a/src/components-examples/material/snack-bar/snack-bar-annotated-component/snack-bar-annotated-component-example.ts +++ b/src/components-examples/material/snack-bar/snack-bar-annotated-component/snack-bar-annotated-component-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import { MatSnackBar, MatSnackBarAction, @@ -23,11 +23,11 @@ import {MatFormFieldModule} from '@angular/material/form-field'; export class SnackBarAnnotatedComponentExample { private _snackBar = inject(MatSnackBar); - durationInSeconds = signal(5); + durationInSeconds = 5; openSnackBar() { this._snackBar.openFromComponent(PizzaPartyAnnotatedComponent, { - duration: this.durationInSeconds() * 1000, + duration: this.durationInSeconds * 1000, }); } } diff --git a/src/components-examples/material/snack-bar/snack-bar-component/snack-bar-component-example.ts b/src/components-examples/material/snack-bar/snack-bar-component/snack-bar-component-example.ts index e681b080c63b..5769ae14e629 100644 --- a/src/components-examples/material/snack-bar/snack-bar-component/snack-bar-component-example.ts +++ b/src/components-examples/material/snack-bar/snack-bar-component/snack-bar-component-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {MatSnackBar} from '@angular/material/snack-bar'; import {MatButtonModule} from '@angular/material/button'; import {MatInputModule} from '@angular/material/input'; @@ -17,11 +17,11 @@ import {MatFormFieldModule} from '@angular/material/form-field'; export class SnackBarComponentExample { private _snackBar = inject(MatSnackBar); - durationInSeconds = signal(5); + durationInSeconds = 5; openSnackBar() { this._snackBar.openFromComponent(PizzaPartyComponent, { - duration: this.durationInSeconds() * 1000, + duration: this.durationInSeconds * 1000, }); } } diff --git a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts index b498890188e1..426bb9b7bcde 100644 --- a/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts +++ b/src/components-examples/material/snack-bar/snack-bar-position/snack-bar-position-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import { MatSnackBar, MatSnackBarHorizontalPosition, @@ -20,13 +20,13 @@ import {MatFormFieldModule} from '@angular/material/form-field'; export class SnackBarPositionExample { private _snackBar = inject(MatSnackBar); - horizontalPosition = signal('start'); - verticalPosition = signal('bottom'); + horizontalPosition: MatSnackBarHorizontalPosition = 'start'; + verticalPosition: MatSnackBarVerticalPosition = 'bottom'; openSnackBar() { this._snackBar.open('Cannonball!!', 'Splash', { - horizontalPosition: this.horizontalPosition(), - verticalPosition: this.verticalPosition(), + horizontalPosition: this.horizontalPosition, + verticalPosition: this.verticalPosition, }); } } diff --git a/src/components-examples/material/sort/sort-harness/sort-harness-example.html b/src/components-examples/material/sort/sort-harness/sort-harness-example.html index 68420425f17c..aa7d631aa4e6 100644 --- a/src/components-examples/material/sort/sort-harness/sort-harness-example.html +++ b/src/components-examples/material/sort/sort-harness/sort-harness-example.html @@ -7,7 +7,7 @@
    - @for (dessert of sortedData(); track dessert) { + @for (dessert of sortedData; track dessert) { diff --git a/src/components-examples/material/sort/sort-harness/sort-harness-example.ts b/src/components-examples/material/sort/sort-harness/sort-harness-example.ts index cee4c011492c..8e3ffe708559 100644 --- a/src/components-examples/material/sort/sort-harness/sort-harness-example.ts +++ b/src/components-examples/material/sort/sort-harness/sort-harness-example.ts @@ -19,21 +19,19 @@ export class SortHarnessExample { {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, ]; - sortedData = signal(this.desserts.slice()); + sortedData = this.desserts.slice(); sortData(sort: Sort) { const data = this.desserts.slice(); if (!sort.active || sort.direction === '') { - this.sortedData.set(data); + this.sortedData = data; } else { - this.sortedData.set( - data.sort((a, b) => { - const aValue = a[sort.active as keyof typeof a]; - const bValue = b[sort.active as keyof typeof b]; - return (aValue < bValue ? -1 : 1) * (sort.direction === 'asc' ? 1 : -1); - }), - ); + this.sortedData = data.sort((a, b) => { + const aValue = (a as any)[sort.active]; + const bValue = (b as any)[sort.active]; + return (aValue < bValue ? -1 : 1) * (sort.direction === 'asc' ? 1 : -1); + }); } } } diff --git a/src/components-examples/material/sort/sort-overview/sort-overview-example.html b/src/components-examples/material/sort/sort-overview/sort-overview-example.html index f4feb0e5e01f..94591483711a 100644 --- a/src/components-examples/material/sort/sort-overview/sort-overview-example.html +++ b/src/components-examples/material/sort/sort-overview/sort-overview-example.html @@ -7,7 +7,7 @@ - @for (dessert of sortedData(); track dessert) { + @for (dessert of sortedData; track dessert) { diff --git a/src/components-examples/material/sort/sort-overview/sort-overview-example.ts b/src/components-examples/material/sort/sort-overview/sort-overview-example.ts index 966bae02cbcf..63d4b7edb82d 100644 --- a/src/components-examples/material/sort/sort-overview/sort-overview-example.ts +++ b/src/components-examples/material/sort/sort-overview/sort-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {Sort, MatSortModule} from '@angular/material/sort'; export interface Dessert { @@ -27,34 +27,32 @@ export class SortOverviewExample { {name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4}, ]; - sortedData = signal(this.desserts.slice()); + sortedData = this.desserts.slice(); sortData(sort: Sort) { const data = this.desserts.slice(); if (!sort.active || sort.direction === '') { - this.sortedData.set(data); + this.sortedData = data; return; } - this.sortedData.set( - data.sort((a, b) => { - const isAsc = sort.direction === 'asc'; - switch (sort.active) { - case 'name': - return compare(a.name, b.name, isAsc); - case 'calories': - return compare(a.calories, b.calories, isAsc); - case 'fat': - return compare(a.fat, b.fat, isAsc); - case 'carbs': - return compare(a.carbs, b.carbs, isAsc); - case 'protein': - return compare(a.protein, b.protein, isAsc); - default: - return 0; - } - }), - ); + this.sortedData = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + case 'calories': + return compare(a.calories, b.calories, isAsc); + case 'fat': + return compare(a.fat, b.fat, isAsc); + case 'carbs': + return compare(a.carbs, b.carbs, isAsc); + case 'protein': + return compare(a.protein, b.protein, isAsc); + default: + return 0; + } + }); } } diff --git a/src/components-examples/material/stepper/index.ts b/src/components-examples/material/stepper/index.ts index db761af897ac..a53ab5c7d909 100644 --- a/src/components-examples/material/stepper/index.ts +++ b/src/components-examples/material/stepper/index.ts @@ -11,4 +11,3 @@ export {StepperLazyContentExample} from './stepper-lazy-content/stepper-lazy-con export {StepperResponsiveExample} from './stepper-responsive/stepper-responsive-example'; export {StepperHeaderPositionExample} from './stepper-header-position/stepper-header-position-example'; export {StepperAnimationsExample} from './stepper-animations/stepper-animations-example'; -export {StepperSignalFormsExample} from './stepper-signal-forms/stepper-signal-forms-example'; diff --git a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html index f5294df6a264..8dc4135bc166 100644 --- a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html +++ b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.html @@ -1,10 +1,10 @@ - - +
    @@ -19,7 +19,7 @@
    - +
    Fill out your address diff --git a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.ts b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.ts index d4c9e12c3c01..19d1e9d4db83 100644 --- a/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.ts +++ b/src/components-examples/material/stepper/stepper-editable/stepper-editable-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {FormBuilder, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -30,6 +30,5 @@ export class StepperEditableExample { secondFormGroup = this._formBuilder.group({ secondCtrl: ['', Validators.required], }); - - isEditable = signal(false); + isEditable = false; } diff --git a/src/components-examples/material/stepper/stepper-intl/stepper-intl-example.ts b/src/components-examples/material/stepper/stepper-intl/stepper-intl-example.ts index d663a3a336a0..fcddcd9417df 100644 --- a/src/components-examples/material/stepper/stepper-intl/stepper-intl-example.ts +++ b/src/components-examples/material/stepper/stepper-intl/stepper-intl-example.ts @@ -1,4 +1,4 @@ -import {Component, Service, inject, signal} from '@angular/core'; +import {Component, Injectable, inject} from '@angular/core'; import {FormBuilder, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatStepperIntl, MatStepperModule} from '@angular/material/stepper'; import {MatButtonModule} from '@angular/material/button'; @@ -6,7 +6,7 @@ import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatRadioModule} from '@angular/material/radio'; -@Service({autoProvided: false}) +@Injectable() export class StepperIntl extends MatStepperIntl { // the default optional label text, if unspecified is "Optional" override optionalLabel = 'Optional Label'; @@ -34,7 +34,7 @@ export class StepperIntlExample { private _formBuilder = inject(FormBuilder); private _matStepperIntl = inject(MatStepperIntl); - optionalLabelText = signal(''); + optionalLabelText = ''; optionalLabelTextChoices: string[] = ['Option 1', 'Option 2', 'Option 3']; firstFormGroup = this._formBuilder.group({ firstCtrl: ['', Validators.required], @@ -44,7 +44,7 @@ export class StepperIntlExample { }); updateOptionalLabel() { - this._matStepperIntl.optionalLabel = this.optionalLabelText(); + this._matStepperIntl.optionalLabel = this.optionalLabelText; // Required for the optional label text to be updated // Notifies the MatStepperIntl service that a change has been made this._matStepperIntl.changes.next(); diff --git a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html index b0fe17d498c7..d177ccc2db90 100644 --- a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html +++ b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.html @@ -1,5 +1,5 @@ - @@ -16,7 +16,7 @@
    - +
    Fill out your address diff --git a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.ts b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.ts index e30d3c21e123..85b7178254a0 100644 --- a/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.ts +++ b/src/components-examples/material/stepper/stepper-optional/stepper-optional-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {FormBuilder, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -30,6 +30,5 @@ export class StepperOptionalExample { secondFormGroup = this._formBuilder.group({ secondCtrl: '', }); - - isOptional = signal(false); + isOptional = false; } diff --git a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html index 61a71c2d6334..a7f6fd0ad978 100644 --- a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html +++ b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.html @@ -1,7 +1,7 @@ - - + Fill out your name diff --git a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.ts b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.ts index 821c69d3c8e7..ddf7037d048e 100644 --- a/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.ts +++ b/src/components-examples/material/stepper/stepper-overview/stepper-overview-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {FormBuilder, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -30,6 +30,5 @@ export class StepperOverviewExample { secondFormGroup = this._formBuilder.group({ secondCtrl: ['', Validators.required], }); - - isLinear = signal(false); + isLinear = false; } diff --git a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.css b/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.css deleted file mode 100644 index 054169ceca1f..000000000000 --- a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.css +++ /dev/null @@ -1,7 +0,0 @@ -.mat-stepper-horizontal { - margin-top: 8px; -} - -.mat-mdc-form-field { - margin-top: 16px; -} diff --git a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.html b/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.html deleted file mode 100644 index 0376d7160051..000000000000 --- a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - Fill out your name - - Name - - -
    - -
    - -
    - -
    - - Address - - -
    - - -
    - -
    - - Done -

    You are now done.

    -
    - - -
    -
    -
    diff --git a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.ts b/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.ts deleted file mode 100644 index 4c35812b90bc..000000000000 --- a/src/components-examples/material/stepper/stepper-signal-forms/stepper-signal-forms-example.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {Component, signal} from '@angular/core'; -import {form, required, FormField} from '@angular/forms/signals'; -import {MatButtonModule} from '@angular/material/button'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MatStepperModule} from '@angular/material/stepper'; - -/** - * @title Stepper using signal forms - */ -@Component({ - selector: 'stepper-signal-forms-example', - templateUrl: 'stepper-signal-forms-example.html', - styleUrl: 'stepper-signal-forms-example.css', - imports: [FormField, MatButtonModule, MatFormFieldModule, MatInputModule, MatStepperModule], -}) -export class StepperSignalFormsExample { - readonly nameFormGroup = form(signal({name: ''}), tree => { - required(tree.name); - }); - - readonly adddressFormGroup = form(signal({address: ''}), tree => { - required(tree.address); - }); - - readonly isLinear = signal(false); -} diff --git a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html index c7fecc24ccf4..1f3aeadb108e 100644 --- a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html +++ b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.html @@ -1,7 +1,7 @@ - - +
    Fill out your name diff --git a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.ts b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.ts index e636f9100987..539a1c185412 100644 --- a/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.ts +++ b/src/components-examples/material/stepper/stepper-vertical/stepper-vertical-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {FormBuilder, Validators, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -30,6 +30,5 @@ export class StepperVerticalExample { secondFormGroup = this._formBuilder.group({ secondCtrl: ['', Validators.required], }); - - isLinear = signal(false); + isLinear = false; } diff --git a/src/components-examples/material/table/table-basic/table-basic-example.html b/src/components-examples/material/table/table-basic/table-basic-example.html index 30054f2ddc2e..054cb0c6ae41 100644 --- a/src/components-examples/material/table/table-basic/table-basic-example.html +++ b/src/components-examples/material/table/table-basic/table-basic-example.html @@ -1,4 +1,4 @@ -
    No. Protein
    {{dessert.name}} {{dessert.calories}}Protein (g)
    {{dessert.name}} {{dessert.calories}}
    +
    diff --git a/src/components-examples/material/table/table-column-styling/table-column-styling-example.html b/src/components-examples/material/table/table-column-styling/table-column-styling-example.html index 7facd6ae856f..db278dd04306 100644 --- a/src/components-examples/material/table/table-column-styling/table-column-styling-example.html +++ b/src/components-examples/material/table/table-column-styling/table-column-styling-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-drag-drop/table-drag-drop-example.html b/src/components-examples/material/table/table-drag-drop/table-drag-drop-example.html index 1c95dbdef749..a1d5dbfce35c 100644 --- a/src/components-examples/material/table/table-drag-drop/table-drag-drop-example.html +++ b/src/components-examples/material/table/table-drag-drop/table-drag-drop-example.html @@ -1,4 +1,4 @@ - diff --git a/src/components-examples/material/table/table-dynamic-array-data/table-dynamic-array-data-example.html b/src/components-examples/material/table/table-dynamic-array-data/table-dynamic-array-data-example.html index 7290d49bff50..d3613b45a00c 100644 --- a/src/components-examples/material/table/table-dynamic-array-data/table-dynamic-array-data-example.html +++ b/src/components-examples/material/table/table-dynamic-array-data/table-dynamic-array-data-example.html @@ -11,7 +11,7 @@ -
    No.
    +
    diff --git a/src/components-examples/material/table/table-dynamic-columns/table-dynamic-columns-example.html b/src/components-examples/material/table/table-dynamic-columns/table-dynamic-columns-example.html index cce67f29186c..0e35b1e1a2fc 100644 --- a/src/components-examples/material/table/table-dynamic-columns/table-dynamic-columns-example.html +++ b/src/components-examples/material/table/table-dynamic-columns/table-dynamic-columns-example.html @@ -2,7 +2,7 @@ -
    No.
    +
    @for (column of displayedColumns; track column) { diff --git a/src/components-examples/material/table/table-dynamic-observable-data/table-dynamic-observable-data-example.html b/src/components-examples/material/table/table-dynamic-observable-data/table-dynamic-observable-data-example.html index 5e06c0ff71f5..6570b63bcb3b 100644 --- a/src/components-examples/material/table/table-dynamic-observable-data/table-dynamic-observable-data-example.html +++ b/src/components-examples/material/table/table-dynamic-observable-data/table-dynamic-observable-data-example.html @@ -11,7 +11,7 @@ -
    {{column}}
    +
    diff --git a/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.html b/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.html index b55ecb342b77..fb9c012b99d2 100644 --- a/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.html +++ b/src/components-examples/material/table/table-expandable-rows/table-expandable-rows-example.html @@ -1,6 +1,6 @@
    No.
    + class="mat-elevation-z8"> @for (column of columnsToDisplay; track column) { diff --git a/src/components-examples/material/table/table-filtering/table-filtering-example.html b/src/components-examples/material/table/table-filtering/table-filtering-example.html index 2175404ec23e..288e7e4306d9 100644 --- a/src/components-examples/material/table/table-filtering/table-filtering-example.html +++ b/src/components-examples/material/table/table-filtering/table-filtering-example.html @@ -3,7 +3,7 @@ -
    {{column}}
    +
    diff --git a/src/components-examples/material/table/table-flex-basic/table-flex-basic-example.html b/src/components-examples/material/table/table-flex-basic/table-flex-basic-example.html index 03c53bd6b9ca..09bb2c212897 100644 --- a/src/components-examples/material/table/table-flex-basic/table-flex-basic-example.html +++ b/src/components-examples/material/table/table-flex-basic/table-flex-basic-example.html @@ -1,4 +1,4 @@ - + No. @@ -25,4 +25,4 @@ - + \ No newline at end of file diff --git a/src/components-examples/material/table/table-flex-large-row/table-flex-large-row-example.html b/src/components-examples/material/table/table-flex-large-row/table-flex-large-row-example.html index 03c53bd6b9ca..09bb2c212897 100644 --- a/src/components-examples/material/table/table-flex-large-row/table-flex-large-row-example.html +++ b/src/components-examples/material/table/table-flex-large-row/table-flex-large-row-example.html @@ -1,4 +1,4 @@ - + No. @@ -25,4 +25,4 @@ - + \ No newline at end of file diff --git a/src/components-examples/material/table/table-footer-row/table-footer-row-example.html b/src/components-examples/material/table/table-footer-row/table-footer-row-example.html index d6a194de99d9..c5faed4ef69d 100644 --- a/src/components-examples/material/table/table-footer-row/table-footer-row-example.html +++ b/src/components-examples/material/table/table-footer-row/table-footer-row-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-generated-columns/table-generated-columns-example.html b/src/components-examples/material/table/table-generated-columns/table-generated-columns-example.html index 46775a4c34f8..110b906e2b9f 100644 --- a/src/components-examples/material/table/table-generated-columns/table-generated-columns-example.html +++ b/src/components-examples/material/table/table-generated-columns/table-generated-columns-example.html @@ -1,4 +1,4 @@ -
    Item
    +
    @for (column of columns; track column) {
    diff --git a/src/components-examples/material/table/table-http/table-http-example.css b/src/components-examples/material/table/table-http/table-http-example.css index 6d12dd92cabd..2f3caee0c42a 100644 --- a/src/components-examples/material/table/table-http/table-http-example.css +++ b/src/components-examples/material/table/table-http/table-http-example.css @@ -5,7 +5,8 @@ .example-table-container { position: relative; - height: 400px; + min-height: 200px; + max-height: 400px; overflow: auto; } diff --git a/src/components-examples/material/table/table-http/table-http-example.html b/src/components-examples/material/table/table-http/table-http-example.html index 2b99a4130b15..a30712cafa22 100644 --- a/src/components-examples/material/table/table-http/table-http-example.html +++ b/src/components-examples/material/table/table-http/table-http-example.html @@ -1,10 +1,10 @@ -
    - @if (isLoadingResults() || isRateLimitReached()) { +
    + @if (isLoadingResults || isRateLimitReached) {
    - @if (isLoadingResults()) { - + @if (isLoadingResults) { + } - @if (isRateLimitReached()) { + @if (isRateLimitReached) {
    GitHub's API rate limit has been reached. It will be reset in one minute.
    @@ -13,15 +13,9 @@ }
    - + +
    @@ -53,9 +47,5 @@
    #
    - +
    diff --git a/src/components-examples/material/table/table-http/table-http-example.ts b/src/components-examples/material/table/table-http/table-http-example.ts index 35c537e5c32a..e982a6083ab1 100644 --- a/src/components-examples/material/table/table-http/table-http-example.ts +++ b/src/components-examples/material/table/table-http/table-http-example.ts @@ -1,6 +1,9 @@ -import {Component, viewChild, signal, computed, resource} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Component, ViewChild, AfterViewInit, inject} from '@angular/core'; import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator'; import {MatSort, MatSortModule, SortDirection} from '@angular/material/sort'; +import {merge, Observable, of as observableOf} from 'rxjs'; +import {catchError, map, startWith, switchMap} from 'rxjs/operators'; import {MatTableModule} from '@angular/material/table'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {DatePipe} from '@angular/common'; @@ -14,54 +17,54 @@ import {DatePipe} from '@angular/common'; templateUrl: 'table-http-example.html', imports: [MatProgressSpinnerModule, MatTableModule, MatSortModule, MatPaginatorModule, DatePipe], }) -export class TableHttpExample { - readonly displayedColumns: string[] = ['created', 'state', 'number', 'title']; - readonly paginator = viewChild.required(MatPaginator); - readonly sort = viewChild.required(MatSort); - readonly sortActive = signal('created'); - readonly sortDirection = signal('desc'); - readonly pageIndex = signal(0); +export class TableHttpExample implements AfterViewInit { + private _httpClient = inject(HttpClient); - readonly issueResource = resource({ - params: () => ({ - sort: this.sortActive(), - order: this.sortDirection(), - page: this.pageIndex(), - }), - loader: async ({params: {sort, order, page}, abortSignal}) => { - const base = 'https://api.github.com/search/issues'; - const requestUrl = `${base}?q=repo:angular/components&sort=${sort}&order=${order}&page=${ - page + 1 - }`; - const response = await fetch(requestUrl, {signal: abortSignal}); - if (!response.ok) { - throw new Error('Rate limit reached'); - } - return (await response.json()) as GithubApi; - }, - }); + displayedColumns: string[] = ['created', 'state', 'number', 'title']; + exampleDatabase: ExampleHttpDatabase | null = null; + data: GithubIssue[] = []; - readonly isLoadingResults = this.issueResource.isLoading; - readonly isRateLimitReached = computed(() => this.issueResource.error() != null); + resultsLength = 0; + isLoadingResults = true; + isRateLimitReached = false; - readonly data = computed(() => { - if (this.isRateLimitReached()) { - return []; - } - return this.issueResource.value()?.items || []; - }); + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; - readonly resultsLength = computed(() => this.issueResource.value()?.total_count || 0); + ngAfterViewInit() { + this.exampleDatabase = new ExampleHttpDatabase(this._httpClient); - handleSort() { - this.paginator().pageIndex = 0; - this.pageIndex.set(0); - this.sortActive.set(this.sort().active); - this.sortDirection.set(this.sort().direction); - } + // If the user changes the sort order, reset back to the first page. + this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.exampleDatabase!.getRepoIssues( + this.sort.active, + this.sort.direction, + this.paginator.pageIndex, + ).pipe(catchError(() => observableOf(null))); + }), + map(data => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + this.isRateLimitReached = data === null; - handlePage() { - this.pageIndex.set(this.paginator().pageIndex); + if (data === null) { + return []; + } + + // Only refresh the result length if there is new data. In case of rate + // limit errors, we do not want to reset the paginator to zero, as that + // would prevent users from re-triggering requests. + this.resultsLength = data.total_count; + return data.items; + }), + ) + .subscribe(data => (this.data = data)); } } @@ -76,3 +79,17 @@ export interface GithubIssue { state: string; title: string; } + +/** An example database that the data source uses to retrieve data for the table. */ +export class ExampleHttpDatabase { + constructor(private _httpClient: HttpClient) {} + + getRepoIssues(sort: string, order: SortDirection, page: number): Observable { + const href = 'https://api.github.com/search/issues'; + const requestUrl = `${href}?q=repo:angular/components&sort=${sort}&order=${order}&page=${ + page + 1 + }`; + + return this._httpClient.get(requestUrl); + } +} diff --git a/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.html b/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.html index 5ddae0bbf606..04b5b162bbb2 100644 --- a/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.html +++ b/src/components-examples/material/table/table-multiple-header-footer/table-multiple-header-footer-example.html @@ -1,4 +1,4 @@ - +
    diff --git a/src/components-examples/material/table/table-multiple-row-template/table-multiple-row-template-example.html b/src/components-examples/material/table/table-multiple-row-template/table-multiple-row-template-example.html index 51003e97f681..2b158e7d8b14 100644 --- a/src/components-examples/material/table/table-multiple-row-template/table-multiple-row-template-example.html +++ b/src/components-examples/material/table/table-multiple-row-template/table-multiple-row-template-example.html @@ -1,4 +1,4 @@ -
    +
    Item
    diff --git a/src/components-examples/material/table/table-overview/table-overview-example.html b/src/components-examples/material/table/table-overview/table-overview-example.html index 3dd4dc8ae8bd..540d30a08cdf 100644 --- a/src/components-examples/material/table/table-overview/table-overview-example.html +++ b/src/components-examples/material/table/table-overview/table-overview-example.html @@ -3,7 +3,7 @@ -
    +
    diff --git a/src/components-examples/material/table/table-pagination/table-pagination-example.html b/src/components-examples/material/table/table-pagination/table-pagination-example.html index 09d83a8a4356..ec67d8f72ee1 100644 --- a/src/components-examples/material/table/table-pagination/table-pagination-example.html +++ b/src/components-examples/material/table/table-pagination/table-pagination-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-recycle-rows/table-recycle-rows-example.html b/src/components-examples/material/table/table-recycle-rows/table-recycle-rows-example.html index e44c0e609d80..d35381653f3a 100644 --- a/src/components-examples/material/table/table-recycle-rows/table-recycle-rows-example.html +++ b/src/components-examples/material/table/table-recycle-rows/table-recycle-rows-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-row-binding/table-row-binding-example.html b/src/components-examples/material/table/table-row-binding/table-row-binding-example.html index 3e166d3f2b8d..26e24ab03718 100644 --- a/src/components-examples/material/table/table-row-binding/table-row-binding-example.html +++ b/src/components-examples/material/table/table-row-binding/table-row-binding-example.html @@ -1,4 +1,4 @@ -
    No.
    +
    diff --git a/src/components-examples/material/table/table-row-context/table-row-context-example.html b/src/components-examples/material/table/table-row-context/table-row-context-example.html index 7c9706566b82..970f6fa5fb87 100644 --- a/src/components-examples/material/table/table-row-context/table-row-context-example.html +++ b/src/components-examples/material/table/table-row-context/table-row-context-example.html @@ -1,4 +1,4 @@ -
    No.
    +
    diff --git a/src/components-examples/material/table/table-selection/table-selection-example.html b/src/components-examples/material/table/table-selection/table-selection-example.html index ab988d2a485a..1a7b2d3e3867 100644 --- a/src/components-examples/material/table/table-selection/table-selection-example.html +++ b/src/components-examples/material/table/table-selection/table-selection-example.html @@ -1,4 +1,4 @@ -
    $implicit
    +
    diff --git a/src/components-examples/material/table/table-sorting/table-sorting-example.html b/src/components-examples/material/table/table-sorting/table-sorting-example.html index 07c3e514723c..76edcae012cb 100644 --- a/src/components-examples/material/table/table-sorting/table-sorting-example.html +++ b/src/components-examples/material/table/table-sorting/table-sorting-example.html @@ -1,5 +1,5 @@
    + class="mat-elevation-z8"> diff --git a/src/components-examples/material/table/table-sticky-columns/table-sticky-columns-example.html b/src/components-examples/material/table/table-sticky-columns/table-sticky-columns-example.html index 84501e12e346..904579979451 100644 --- a/src/components-examples/material/table/table-sticky-columns/table-sticky-columns-example.html +++ b/src/components-examples/material/table/table-sticky-columns/table-sticky-columns-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-sticky-complex-flex/table-sticky-complex-flex-example.html b/src/components-examples/material/table/table-sticky-complex-flex/table-sticky-complex-flex-example.html index 19da35c58c00..b6f9c0befe8f 100644 --- a/src/components-examples/material/table/table-sticky-complex-flex/table-sticky-complex-flex-example.html +++ b/src/components-examples/material/table/table-sticky-complex-flex/table-sticky-complex-flex-example.html @@ -35,7 +35,7 @@ -
    +
    @for (table of tables; track table) { diff --git a/src/components-examples/material/table/table-sticky-complex/table-sticky-complex-example.html b/src/components-examples/material/table/table-sticky-complex/table-sticky-complex-example.html index 97d663f0f68c..abe0614ab890 100644 --- a/src/components-examples/material/table/table-sticky-complex/table-sticky-complex-example.html +++ b/src/components-examples/material/table/table-sticky-complex/table-sticky-complex-example.html @@ -35,7 +35,7 @@ -
    +
    @for (table of tables; track table) {
    diff --git a/src/components-examples/material/table/table-sticky-footer/table-sticky-footer-example.html b/src/components-examples/material/table/table-sticky-footer/table-sticky-footer-example.html index c244d683a91f..48de652e8b71 100644 --- a/src/components-examples/material/table/table-sticky-footer/table-sticky-footer-example.html +++ b/src/components-examples/material/table/table-sticky-footer/table-sticky-footer-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-sticky-header/table-sticky-header-example.html b/src/components-examples/material/table/table-sticky-header/table-sticky-header-example.html index e8979e61dbf3..633706d39229 100644 --- a/src/components-examples/material/table/table-sticky-header/table-sticky-header-example.html +++ b/src/components-examples/material/table/table-sticky-header/table-sticky-header-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-text-column-advanced/table-text-column-advanced-example.html b/src/components-examples/material/table/table-text-column-advanced/table-text-column-advanced-example.html index 86633d16458b..a609db477db1 100644 --- a/src/components-examples/material/table/table-text-column-advanced/table-text-column-advanced-example.html +++ b/src/components-examples/material/table/table-text-column-advanced/table-text-column-advanced-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-text-column/table-text-column-example.html b/src/components-examples/material/table/table-text-column/table-text-column-example.html index 1bdebc60b4c1..427692975ad0 100644 --- a/src/components-examples/material/table/table-text-column/table-text-column-example.html +++ b/src/components-examples/material/table/table-text-column/table-text-column-example.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/table/table-with-ripples/table-with-ripples-example.html b/src/components-examples/material/table/table-with-ripples/table-with-ripples-example.html index f0c0f055176a..0a2704e8a7b2 100644 --- a/src/components-examples/material/table/table-with-ripples/table-with-ripples-example.html +++ b/src/components-examples/material/table/table-with-ripples/table-with-ripples-example.html @@ -1,4 +1,4 @@ - + Name {{element.name}} diff --git a/src/components-examples/material/table/table-wrapped/wrapper-table.html b/src/components-examples/material/table/table-wrapped/wrapper-table.html index 132ddba1ad51..ed1477b2b513 100644 --- a/src/components-examples/material/table/table-wrapped/wrapper-table.html +++ b/src/components-examples/material/table/table-wrapped/wrapper-table.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/components-examples/material/tabs/tab-group-async/tab-group-async-example.ts b/src/components-examples/material/tabs/tab-group-async/tab-group-async-example.ts index 1d1d6c4b9c0d..70f51e7490e0 100644 --- a/src/components-examples/material/tabs/tab-group-async/tab-group-async-example.ts +++ b/src/components-examples/material/tabs/tab-group-async/tab-group-async-example.ts @@ -1,8 +1,7 @@ import {Component} from '@angular/core'; -import {Observable, of} from 'rxjs'; +import {Observable, Observer} from 'rxjs'; import {MatTabsModule} from '@angular/material/tabs'; import {AsyncPipe} from '@angular/common'; -import {delay} from 'rxjs/operators'; export interface ExampleTab { label: string; @@ -21,10 +20,14 @@ export class TabGroupAsyncExample { asyncTabs: Observable; constructor() { - this.asyncTabs = of([ - {label: 'First', content: 'Content 1'}, - {label: 'Second', content: 'Content 2'}, - {label: 'Third', content: 'Content 3'}, - ]).pipe(delay(1000)); + this.asyncTabs = new Observable((observer: Observer) => { + setTimeout(() => { + observer.next([ + {label: 'First', content: 'Content 1'}, + {label: 'Second', content: 'Content 2'}, + {label: 'Third', content: 'Content 3'}, + ]); + }, 1000); + }); } } diff --git a/src/components-examples/material/tabs/tab-group-drag-drop/tab-group-drag-drop-example.html b/src/components-examples/material/tabs/tab-group-drag-drop/tab-group-drag-drop-example.html index 3011b5cdfbb7..4d7007e71809 100644 --- a/src/components-examples/material/tabs/tab-group-drag-drop/tab-group-drag-drop-example.html +++ b/src/components-examples/material/tabs/tab-group-drag-drop/tab-group-drag-drop-example.html @@ -6,7 +6,7 @@ class="example-drag-tabs" [(selectedIndex)]="selectedTabIndex" animationDuration="0"> - @for (tab of tabs(); track $index) { + @for (tab of tabs; track $index) { ) { - const prevActive = this.tabs()[this.selectedTabIndex()]; - moveItemInArray(this.tabs(), event.previousIndex, event.currentIndex); - this.selectedTabIndex.set(this.tabs().indexOf(prevActive)); + const prevActive = this.tabs[this.selectedTabIndex]; + moveItemInArray(this.tabs, event.previousIndex, event.currentIndex); + this.selectedTabIndex = this.tabs.indexOf(prevActive); } } diff --git a/src/components-examples/material/tabs/tab-group-dynamic-height/tab-group-dynamic-height-example.html b/src/components-examples/material/tabs/tab-group-dynamic-height/tab-group-dynamic-height-example.html index 7d63537d84af..312652076aa0 100644 --- a/src/components-examples/material/tabs/tab-group-dynamic-height/tab-group-dynamic-height-example.html +++ b/src/components-examples/material/tabs/tab-group-dynamic-height/tab-group-dynamic-height-example.html @@ -2,12 +2,12 @@ -
    +
    Small content
    -
    +
    Large content
    diff --git a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html index 5351d28c5856..f4e8d3e46e3a 100644 --- a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html +++ b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.html @@ -14,13 +14,13 @@ - @for (tab of tabs(); track $index; let index = $index) { + @for (tab of tabs; track tab; let index = $index) { Contents for {{tab}} tab diff --git a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.ts b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.ts index ef760d5db1a6..de18cf4af048 100644 --- a/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.ts +++ b/src/components-examples/material/tabs/tab-group-dynamic/tab-group-dynamic-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatTabsModule} from '@angular/material/tabs'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -24,19 +24,19 @@ import {MatFormFieldModule} from '@angular/material/form-field'; ], }) export class TabGroupDynamicExample { - tabs = signal(['First', 'Second', 'Third']); + tabs = ['First', 'Second', 'Third']; selected = new FormControl(0); addTab(selectAfterAdding: boolean) { - this.tabs.update(tabs => [...tabs, 'New']); + this.tabs.push('New'); if (selectAfterAdding) { - this.selected.setValue(this.tabs().length - 1); + this.selected.setValue(this.tabs.length - 1); } } removeTab(index: number) { - this.tabs.update(tabs => tabs.filter((_, i) => i !== index)); + this.tabs.splice(index, 1); this.selected.setValue(index); } } diff --git a/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.html b/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.html index e304a4bfc9ab..84f40a6021c9 100644 --- a/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.html +++ b/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.html @@ -1,23 +1,19 @@ - - + + - Content 1 - Loaded: {{tabLoadTimes()[1] | date:'medium'}} + Content 1 - Loaded: {{getTimeLoaded(1) | date:'medium'}} - + - Content 2 - Loaded: {{tabLoadTimes()[2] | date:'medium'}} + Content 2 - Loaded: {{getTimeLoaded(2) | date:'medium'}} - Content 3 - Loaded: {{tabLoadTimes()[3] | date:'medium'}} + Content 3 - Loaded: {{getTimeLoaded(3) | date:'medium'}} - - \ No newline at end of file diff --git a/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.ts b/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.ts index 735ae81d54fe..f2c0ac853b87 100644 --- a/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.ts +++ b/src/components-examples/material/tabs/tab-group-lazy-loaded/tab-group-lazy-loaded-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {DatePipe} from '@angular/common'; import {MatTabsModule} from '@angular/material/tabs'; @@ -11,18 +11,13 @@ import {MatTabsModule} from '@angular/material/tabs'; imports: [MatTabsModule, DatePipe], }) export class TabGroupLazyLoadedExample { - protected tabLoadTimes = signal>({ - 1: new Date(), - }); + tabLoadTimes: Date[] = []; - protected markTimeLoaded(index: number) { - this.tabLoadTimes.update(current => - current[index] - ? current - : { - ...current, - [index]: new Date(), - }, - ); + getTimeLoaded(index: number) { + if (!this.tabLoadTimes[index]) { + this.tabLoadTimes[index] = new Date(); + } + + return this.tabLoadTimes[index]; } } diff --git a/src/components-examples/material/tabs/tab-group-stretched/tab-group-stretched-example.html b/src/components-examples/material/tabs/tab-group-stretched/tab-group-stretched-example.html index b06b5aea3c77..048ae2c47d42 100644 --- a/src/components-examples/material/tabs/tab-group-stretched/tab-group-stretched-example.html +++ b/src/components-examples/material/tabs/tab-group-stretched/tab-group-stretched-example.html @@ -1,4 +1,4 @@ - + Content 1 Content 2 Content 3 diff --git a/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.html b/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.html index a4a701bef4cf..9b90615a2573 100644 --- a/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.html +++ b/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.html @@ -1,9 +1,9 @@ diff --git a/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.ts b/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.ts index 9c297fe52c14..15436e22e03f 100644 --- a/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.ts +++ b/src/components-examples/material/tabs/tab-nav-bar-basic/tab-nav-bar-basic-example.ts @@ -1,4 +1,4 @@ -import {Component, linkedSignal, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatTabsModule} from '@angular/material/tabs'; @@ -12,10 +12,10 @@ import {MatTabsModule} from '@angular/material/tabs'; imports: [MatTabsModule, MatButtonModule], }) export class TabNavBarBasicExample { - links = signal(['Link 1', 'Link 2', 'Link 3']); - activeLink = linkedSignal(() => this.links()[0]); + links = ['First', 'Second', 'Third']; + activeLink = this.links[0]; addLink() { - this.links.update(current => [...current, `Link ${current.length + 1}`]); + this.links.push(`Link ${this.links.length + 1}`); } } diff --git a/src/components-examples/material/timepicker/timepicker-custom-icon/timepicker-custom-icon-example.ts b/src/components-examples/material/timepicker/timepicker-custom-icon/timepicker-custom-icon-example.ts index 3a9f5b8528c3..4f356aade0c0 100644 --- a/src/components-examples/material/timepicker/timepicker-custom-icon/timepicker-custom-icon-example.ts +++ b/src/components-examples/material/timepicker/timepicker-custom-icon/timepicker-custom-icon-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatIcon} from '@angular/material/icon'; import {MatInputModule} from '@angular/material/input'; @@ -11,5 +11,6 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'timepicker-custom-icon-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule, MatIcon], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerCustomIconExample {} diff --git a/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.html b/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.html index 1daa1020afb8..b33725aa847a 100644 --- a/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.html +++ b/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.html @@ -15,4 +15,4 @@ -

    Value: {{value()}}

    +

    Value: {{value}}

    diff --git a/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.ts b/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.ts index adbab77b49be..93551951480d 100644 --- a/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.ts +++ b/src/components-examples/material/timepicker/timepicker-datepicker-integration/timepicker-datepicker-integration-example.ts @@ -1,4 +1,4 @@ -import {Component, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; @@ -19,7 +19,8 @@ import {MatDatepickerModule} from '@angular/material/datepicker'; MatDatepickerModule, FormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerDatepickerIntegrationExample { - value = signal(undefined); + value!: Date; } diff --git a/src/components-examples/material/timepicker/timepicker-forms/timepicker-forms-example.ts b/src/components-examples/material/timepicker/timepicker-forms/timepicker-forms-example.ts index 02348cb66758..60ce8d3a6aec 100644 --- a/src/components-examples/material/timepicker/timepicker-forms/timepicker-forms-example.ts +++ b/src/components-examples/material/timepicker/timepicker-forms/timepicker-forms-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; @@ -11,6 +11,7 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'timepicker-forms-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule, ReactiveFormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerFormsExample { formControl: FormControl; diff --git a/src/components-examples/material/timepicker/timepicker-harness/timepicker-harness-example.ts b/src/components-examples/material/timepicker/timepicker-harness/timepicker-harness-example.ts index 68971c20cec2..33debcb9cc3f 100644 --- a/src/components-examples/material/timepicker/timepicker-harness/timepicker-harness-example.ts +++ b/src/components-examples/material/timepicker/timepicker-harness/timepicker-harness-example.ts @@ -1,4 +1,4 @@ -import {Component, Signal, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Signal, signal} from '@angular/core'; import {provideNativeDateAdapter} from '@angular/material/core'; import {MatTimepickerModule} from '@angular/material/timepicker'; @@ -10,6 +10,7 @@ import {MatTimepickerModule} from '@angular/material/timepicker'; templateUrl: 'timepicker-harness-example.html', providers: [provideNativeDateAdapter()], imports: [MatTimepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerHarnessExample { date: Signal; diff --git a/src/components-examples/material/timepicker/timepicker-locale/timepicker-locale-example.ts b/src/components-examples/material/timepicker/timepicker-locale/timepicker-locale-example.ts index b105549d3d2d..18217aebaada 100644 --- a/src/components-examples/material/timepicker/timepicker-locale/timepicker-locale-example.ts +++ b/src/components-examples/material/timepicker/timepicker-locale/timepicker-locale-example.ts @@ -1,4 +1,4 @@ -import {Component, inject, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; @@ -12,10 +12,11 @@ import {MatButtonModule} from '@angular/material/button'; templateUrl: 'timepicker-locale-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule, FormsModule, MatButtonModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerLocaleExample { private readonly _adapter = inject>(DateAdapter); - value = signal(new Date(2024, 0, 1, 13, 45, 0)); + value = new Date(2024, 0, 1, 13, 45, 0); protected switchLocale() { this._adapter.setLocale('bg-BG'); diff --git a/src/components-examples/material/timepicker/timepicker-options/timepicker-options-example.ts b/src/components-examples/material/timepicker/timepicker-options/timepicker-options-example.ts index a0c1e0c82d76..ae75213b1b55 100644 --- a/src/components-examples/material/timepicker/timepicker-options/timepicker-options-example.ts +++ b/src/components-examples/material/timepicker/timepicker-options/timepicker-options-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatTimepickerModule, MatTimepickerOption} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,6 +10,7 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'timepicker-options-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerOptionsExample { customOptions: MatTimepickerOption[] = [ diff --git a/src/components-examples/material/timepicker/timepicker-overview/timepicker-overview-example.ts b/src/components-examples/material/timepicker/timepicker-overview/timepicker-overview-example.ts index add32b42cf00..8f6f798f7978 100644 --- a/src/components-examples/material/timepicker/timepicker-overview/timepicker-overview-example.ts +++ b/src/components-examples/material/timepicker/timepicker-overview/timepicker-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatTimepickerModule} from '@angular/material/timepicker'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -10,5 +10,6 @@ import {provideNativeDateAdapter} from '@angular/material/core'; templateUrl: 'timepicker-overview-example.html', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerOverviewExample {} diff --git a/src/components-examples/material/timepicker/timepicker-validation/timepicker-validation-example.ts b/src/components-examples/material/timepicker/timepicker-validation/timepicker-validation-example.ts index 43a19d0873bb..9702b1be034f 100644 --- a/src/components-examples/material/timepicker/timepicker-validation/timepicker-validation-example.ts +++ b/src/components-examples/material/timepicker/timepicker-validation/timepicker-validation-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {JsonPipe} from '@angular/common'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatTimepickerModule} from '@angular/material/timepicker'; @@ -13,6 +13,7 @@ import {provideNativeDateAdapter} from '@angular/material/core'; styleUrl: './timepicker-validation-example.css', providers: [provideNativeDateAdapter()], imports: [MatFormFieldModule, MatInputModule, MatTimepickerModule, ReactiveFormsModule, JsonPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TimepickerValidationExample { formControl = new FormControl(null); diff --git a/src/components-examples/material/toolbar/index.ts b/src/components-examples/material/toolbar/index.ts index 83da64aa87a5..e9f17b3c638c 100644 --- a/src/components-examples/material/toolbar/index.ts +++ b/src/components-examples/material/toolbar/index.ts @@ -3,4 +3,3 @@ export {ToolbarMultirowExample} from './toolbar-multirow/toolbar-multirow-exampl export {ToolbarSimpleExample} from './toolbar-simple/toolbar-simple-example'; export {ToolbarOverviewExample} from './toolbar-overview/toolbar-overview-example'; export {ToolbarHarnessExample} from './toolbar-harness/toolbar-harness-example'; -export {ToolbarNavbarExample} from './toolbar-navbar/toolbar-navbar-example'; diff --git a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.css b/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.css deleted file mode 100644 index 9633d1593c37..000000000000 --- a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.css +++ /dev/null @@ -1,3 +0,0 @@ -.example-spacer { - flex: 1 1 auto; -} diff --git a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.html b/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.html deleted file mode 100644 index deb31b60dfd0..000000000000 --- a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.html +++ /dev/null @@ -1,10 +0,0 @@ - - My App - - - - Home - Products - Pricing - About - diff --git a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.ts b/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.ts deleted file mode 100644 index f1b6862c9538..000000000000 --- a/src/components-examples/material/toolbar/toolbar-navbar/toolbar-navbar-example.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Component} from '@angular/core'; -import {MatButtonModule} from '@angular/material/button'; -import {MatToolbarModule} from '@angular/material/toolbar'; - -/** - * @title Toolbar as a navigation bar - */ -@Component({ - selector: 'toolbar-navbar-example', - templateUrl: 'toolbar-navbar-example.html', - styleUrl: 'toolbar-navbar-example.css', - imports: [MatToolbarModule, MatButtonModule], -}) -export class ToolbarNavbarExample {} diff --git a/src/components-examples/material/tooltip/tooltip-delay/tooltip-delay-example.css b/src/components-examples/material/tooltip/tooltip-delay/tooltip-delay-example.css index 3f001a328f92..5a5b8845b187 100644 --- a/src/components-examples/material/tooltip/tooltip-delay/tooltip-delay-example.css +++ b/src/components-examples/material/tooltip/tooltip-delay/tooltip-delay-example.css @@ -1,3 +1,4 @@ -.example-user-input { - margin: 0 8px 8px 0; +.mat-form-field + .mat-form-field, +.mat-raised-button { + margin-left: 8px; } diff --git a/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css index 01834af4a473..d38c73d854fb 100644 --- a/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css +++ b/src/components-examples/material/tooltip/tooltip-position-at-origin/tooltip-position-at-origin-example.css @@ -1,5 +1,8 @@ button.demo-button { - width: 200px; - height: 200px; - margin: 0 8px 8px 0; + width: 500px; + height: 500px; +} + +.example-enabled-checkbox { + margin-left: 8px; } diff --git a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html index ffec8e6f66ef..c1ed7696fe5f 100644 --- a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html +++ b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html @@ -1,18 +1,21 @@ - + - {{node.name}} + {{node.item}} - - {{node.name}} + {{node.item}} @if (node.isLoading()) { - + } diff --git a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts index 2a72216ea8a3..84698afd315e 100644 --- a/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts +++ b/src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.ts @@ -1,23 +1,28 @@ -import {Component, Service, WritableSignal, inject, signal} from '@angular/core'; +import {CollectionViewer, SelectionChange, DataSource} from '@angular/cdk/collections'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {ChangeDetectionStrategy, Component, Injectable, inject, signal} from '@angular/core'; +import {BehaviorSubject, merge, Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; import {MatProgressBarModule} from '@angular/material/progress-bar'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; import {MatTreeModule} from '@angular/material/tree'; -/** Node with expandable and level information */ -interface DynamicNode { - name: string; - level: number; - expandable: boolean; - isLoading: WritableSignal; - children?: DynamicNode[]; +/** Flat node with expandable and level information */ +class DynamicFlatNode { + constructor( + public item: string, + public level = 1, + public expandable = false, + public isLoading = signal(false), + ) {} } /** * Database for dynamic data. When expanding a node in the tree, the data source will need to fetch * the descendants data from the database. */ -@Service() +@Injectable({providedIn: 'root'}) export class DynamicDatabase { dataMap = new Map([ ['Fruits', ['Apple', 'Orange', 'Banana']], @@ -29,26 +34,102 @@ export class DynamicDatabase { rootLevelNodes: string[] = ['Fruits', 'Vegetables']; /** Initial data from database */ - initialData(): DynamicNode[] { - return this.rootLevelNodes.map(name => this.createNode(name, 0, true)); + initialData(): DynamicFlatNode[] { + return this.rootLevelNodes.map(name => new DynamicFlatNode(name, 0, true)); } - createNode(name: string, level: number, expandable: boolean): DynamicNode { - return { - name, - level, - expandable, - isLoading: signal(false), - children: undefined, - }; + getChildren(node: string): string[] | undefined { + return this.dataMap.get(node); } - getChildren(name: string): string[] | undefined { - return this.dataMap.get(name); + isExpandable(node: string): boolean { + return this.dataMap.has(node); } +} +/** + * File database, it can build a tree structured Json object from string. + * Each node in Json object represents a file or a directory. For a file, it has filename and type. + * For a directory, it has filename and children (a list of files or directories). + * The input will be a json object string, and the output is a list of `FileNode` with nested + * structure. + */ +export class DynamicDataSource implements DataSource { + dataChange = new BehaviorSubject([]); - isExpandable(name: string): boolean { - return this.dataMap.has(name); + get data(): DynamicFlatNode[] { + return this.dataChange.value; + } + set data(value: DynamicFlatNode[]) { + this._treeControl.dataNodes = value; + this.dataChange.next(value); + } + + constructor( + private _treeControl: FlatTreeControl, + private _database: DynamicDatabase, + ) {} + + connect(collectionViewer: CollectionViewer): Observable { + this._treeControl.expansionModel.changed.subscribe(change => { + if ( + (change as SelectionChange).added || + (change as SelectionChange).removed + ) { + this.handleTreeControl(change as SelectionChange); + } + }); + + return merge(collectionViewer.viewChange, this.dataChange).pipe(map(() => this.data)); + } + + disconnect(collectionViewer: CollectionViewer): void {} + + /** Handle expand/collapse behaviors */ + handleTreeControl(change: SelectionChange) { + if (change.added) { + change.added.forEach(node => this.toggleNode(node, true)); + } + if (change.removed) { + change.removed + .slice() + .reverse() + .forEach(node => this.toggleNode(node, false)); + } + } + + /** + * Toggle the node, remove from display list + */ + toggleNode(node: DynamicFlatNode, expand: boolean) { + const children = this._database.getChildren(node.item); + const index = this.data.indexOf(node); + if (!children || index < 0) { + // If no children, or cannot find the node, no op + return; + } + + node.isLoading.set(true); + + setTimeout(() => { + if (expand) { + const nodes = children.map( + name => new DynamicFlatNode(name, node.level + 1, this._database.isExpandable(name)), + ); + this.data.splice(index + 1, 0, ...nodes); + } else { + let count = 0; + for ( + let i = index + 1; + i < this.data.length && this.data[i].level > node.level; + i++, count++ + ) {} + this.data.splice(index + 1, count); + } + + // notify the change + this.dataChange.next(this.data); + node.isLoading.set(false); + }, 1000); } } @@ -60,37 +141,25 @@ export class DynamicDatabase { templateUrl: 'tree-dynamic-example.html', styleUrl: 'tree-dynamic-example.css', imports: [MatTreeModule, MatButtonModule, MatIconModule, MatProgressBarModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeDynamicExample { - private _database = inject(DynamicDatabase); - readonly dataSource = signal(this._database.initialData()); - readonly childrenAccessor = (node: DynamicNode) => node.children ?? []; - readonly hasChild = (_: number, node: DynamicNode) => node.expandable; + constructor() { + const database = inject(DynamicDatabase); - /** - * Load children on node expansion. - * Called from template via (expandedChange) output. - */ - onNodeExpanded(node: DynamicNode, expanded: boolean): void { - if (!expanded || node.children) { - // Don't reload if collapsing or already loaded - return; - } + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + this.dataSource = new DynamicDataSource(this.treeControl, database); - const childNames = this._database.getChildren(node.name); - if (!childNames) { - return; - } + this.dataSource.data = database.initialData(); + } - node.isLoading.set(true); + treeControl: FlatTreeControl; - // Simulate async data loading - setTimeout(() => { - node.children = childNames.map(name => - this._database.createNode(name, node.level + 1, this._database.isExpandable(name)), - ); - node.isLoading.set(false); - this.dataSource.set([...this.dataSource()]); - }, 1000); - } + dataSource: DynamicDataSource; + + getLevel = (node: DynamicFlatNode) => node.level; + + isExpandable = (node: DynamicFlatNode) => node.expandable; + + hasChild = (_: number, _nodeData: DynamicFlatNode) => _nodeData.expandable; } diff --git a/src/components-examples/material/tree/tree-flat-child-accessor-overview/tree-flat-child-accessor-overview-example.ts b/src/components-examples/material/tree/tree-flat-child-accessor-overview/tree-flat-child-accessor-overview-example.ts index 1003b4f7172c..17d0c50eb649 100644 --- a/src/components-examples/material/tree/tree-flat-child-accessor-overview/tree-flat-child-accessor-overview-example.ts +++ b/src/components-examples/material/tree/tree-flat-child-accessor-overview/tree-flat-child-accessor-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatTreeModule} from '@angular/material/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -19,6 +19,7 @@ interface FoodNode { selector: 'tree-flat-child-accessor-overview-example', templateUrl: 'tree-flat-child-accessor-overview-example.html', imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeFlatChildAccessorOverviewExample { dataSource = EXAMPLE_DATA; diff --git a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html index 3d4da1ac46ad..e70aba031ae6 100644 --- a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html +++ b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html @@ -1,4 +1,4 @@ - + @@ -11,7 +11,7 @@ {{node.name}} diff --git a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts index cacfb4dfa982..4211f713c539 100644 --- a/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts +++ b/src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.ts @@ -1,5 +1,6 @@ -import {Component} from '@angular/core'; -import {MatTreeModule} from '@angular/material/tree'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -12,6 +13,13 @@ interface FoodNode { children?: FoodNode[]; } +/** Flat node with expandable and level information */ +interface ExampleFlatNode { + expandable: boolean; + name: string; + level: number; +} + /** * @title Tree with flat nodes */ @@ -19,13 +27,36 @@ interface FoodNode { selector: 'tree-flat-overview-example', templateUrl: 'tree-flat-overview-example.html', imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeFlatOverviewExample { - dataSource = EXAMPLE_DATA; + private _transformer = (node: FoodNode, level: number) => { + return { + expandable: !!node.children && node.children.length > 0, + name: node.name, + level: level, + }; + }; + + treeControl = new FlatTreeControl( + node => node.level, + node => node.expandable, + ); + + treeFlattener = new MatTreeFlattener( + this._transformer, + node => node.level, + node => node.expandable, + node => node.children, + ); + + dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - childrenAccessor = (node: FoodNode) => node.children ?? []; + constructor() { + this.dataSource.data = EXAMPLE_DATA; + } - hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0; + hasChild = (_: number, node: ExampleFlatNode) => node.expandable; } const EXAMPLE_DATA: FoodNode[] = [ diff --git a/src/components-examples/material/tree/tree-harness/tree-harness-example.html b/src/components-examples/material/tree/tree-harness/tree-harness-example.html index b500f1fc2bc5..e70aba031ae6 100644 --- a/src/components-examples/material/tree/tree-harness/tree-harness-example.html +++ b/src/components-examples/material/tree/tree-harness/tree-harness-example.html @@ -1,4 +1,4 @@ - + @@ -6,15 +6,12 @@ {{node.name}} - - {{node.name}} diff --git a/src/components-examples/material/tree/tree-harness/tree-harness-example.ts b/src/components-examples/material/tree/tree-harness/tree-harness-example.ts index 9052ec91cb8d..8f3b4b4cd40f 100644 --- a/src/components-examples/material/tree/tree-harness/tree-harness-example.ts +++ b/src/components-examples/material/tree/tree-harness/tree-harness-example.ts @@ -1,5 +1,6 @@ -import {Component} from '@angular/core'; -import {MatTreeModule} from '@angular/material/tree'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -8,6 +9,12 @@ interface Node { children?: Node[]; } +interface ExampleFlatNode { + expandable: boolean; + name: string; + level: number; +} + /** * @title Testing with MatTreeHarness */ @@ -15,13 +22,36 @@ interface Node { selector: 'tree-harness-example', templateUrl: 'tree-harness-example.html', imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeHarnessExample { - dataSource = EXAMPLE_DATA; + private _transformer = (node: Node, level: number) => { + return { + expandable: !!node.children && node.children.length > 0, + name: node.name, + level: level, + }; + }; + + treeControl = new FlatTreeControl( + node => node.level, + node => node.expandable, + ); + + treeFlattener = new MatTreeFlattener( + this._transformer, + node => node.level, + node => node.expandable, + node => node.children, + ); + + dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - childrenAccessor = (node: Node) => node.children ?? []; + constructor() { + this.dataSource.data = EXAMPLE_DATA; + } - hasChild = (_: number, node: Node) => !!node.children && node.children.length > 0; + hasChild = (_: number, node: ExampleFlatNode) => node.expandable; } const EXAMPLE_DATA: Node[] = [ diff --git a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts b/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts index e1cb5343c9cf..fe0150c1bbc8 100644 --- a/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts +++ b/src/components-examples/material/tree/tree-legacy-keyboard-interface/tree-legacy-keyboard-interface-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ArrayDataSource} from '@angular/cdk/collections'; import {FlatTreeControl} from '@angular/cdk/tree'; import {MatIconModule} from '@angular/material/icon'; @@ -22,6 +22,7 @@ interface ExampleFlatNode { styleUrls: ['tree-legacy-keyboard-interface-example.css'], imports: [MatTreeModule, MatButtonModule, MatIconModule], providers: [NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeLegacyKeyboardInterfaceExample { treeControl = new FlatTreeControl( diff --git a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html index 14c41e3f0c64..88bf949e1737 100644 --- a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html +++ b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html @@ -1,4 +1,4 @@ - + @@ -7,17 +7,20 @@ - {{node.name}} - + Load more of {{node.parent}}... - \ No newline at end of file + diff --git a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts index 7aab3ccfbd02..da8da8b5b15a 100644 --- a/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts +++ b/src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.ts @@ -5,32 +5,56 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {Component, Service, inject, signal} from '@angular/core'; -import {MatTreeModule} from '@angular/material/tree'; +import {FlatTreeControl} from '@angular/cdk/tree'; +import {ChangeDetectionStrategy, Component, Injectable, inject} from '@angular/core'; +import {MatTreeFlatDataSource, MatTreeFlattener, MatTreeModule} from '@angular/material/tree'; +import {BehaviorSubject, Observable} from 'rxjs'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; import {ENTER, SPACE} from '@angular/cdk/keycodes'; -/** Node data with optional children */ -interface TreeNode { - name: string; - parent: string | null; - expandable: boolean; - isLoadMore: boolean; - children?: TreeNode[]; +let loadMoreId = 1; + +/** Nested node */ +class NestedNode { + childrenChange = new BehaviorSubject([]); + + get children(): NestedNode[] { + return this.childrenChange.value; + } + + constructor( + public name: string, + public hasChildren = false, + public parent: string | null = null, + public isLoadMore = false, + ) {} +} + +/** Flat node with expandable and level information */ +export class FlatNode { + constructor( + public name: string, + public level = 1, + public expandable = false, + public parent: string | null = null, + public isLoadMore = false, + ) {} } /** Number of nodes loaded at a time */ const batchSize = 3; /** - * A database that only loads part of the data initially. After user clicks on the `Load more` + * A database that only load part of the data initially. After user clicks on the `Load more` * button, more data will be loaded. */ -@Service({autoProvided: false}) +@Injectable() export class LoadmoreDatabase { /** Map of node name to node */ - private _nodes = new Map(); + nodes = new Map(); + + dataChange = new BehaviorSubject([]); /** Example data */ rootNodes: string[] = ['Vegetables', 'Fruits']; @@ -56,53 +80,42 @@ export class LoadmoreDatabase { ['Onion', ['Yellow', 'White', 'Purple', 'Green', 'Shallot', 'Sweet', 'Red', 'Leek']], ]); - initialize(): TreeNode[] { - return this.rootNodes.map(name => this._getOrCreateNode(name, null)); + initialize() { + const data = this.rootNodes.map(name => this._generateNode(name, null)); + this.dataChange.next(data); } - private _getOrCreateNode(name: string, parent: string | null): TreeNode { - if (!this._nodes.has(name)) { - this._nodes.set(name, { - name, - parent, - expandable: this.childMap.has(name), - isLoadMore: false, - children: undefined, - }); + /** Expand a node whose children are not loaded */ + loadChildren(name: string, onlyFirstTime = false) { + if (!this.nodes.has(name) || !this.childMap.has(name)) { + return; } - return this._nodes.get(name)!; - } + const parent = this.nodes.get(name)!; + const children = this.childMap.get(name)!; - /** Load children for a node, with pagination support */ - loadChildren(parentName: string, onlyFirstTime = false): void { - const parent = this._nodes.get(parentName); - const childNames = this.childMap.get(parentName); - if (!parent || !childNames) { + if (onlyFirstTime && parent.children!.length > 0) { return; } - if (onlyFirstTime && parent.children && parent.children.length > 0) { - return; + const newChildrenNumber = parent.children!.length + batchSize; + const nodes = children + .slice(0, newChildrenNumber) + .map(name => this._generateNode(name, parent.name)); + if (newChildrenNumber < children.length) { + // Need a new "Load More" node + nodes.push(new NestedNode(`LOAD_MORE-${loadMoreId++}`, false, name, true)); } - const currentChildCount = parent.children?.filter(c => !c.isLoadMore).length ?? 0; - const newChildCount = currentChildCount + batchSize; - - const children = childNames - .slice(0, newChildCount) - .map(name => this._getOrCreateNode(name, parentName)); - - // Add "Load more" node if there are more children - if (newChildCount < childNames.length) { - children.push({ - name: `LOAD_MORE_${parentName}_${Date.now()}`, - parent: parentName, - expandable: false, - isLoadMore: true, - }); + parent.childrenChange.next(nodes); + this.dataChange.next(this.dataChange.value); + } + + private _generateNode(name: string, parent: string | null): NestedNode { + if (!this.nodes.has(name)) { + this.nodes.set(name, new NestedNode(name, this.childMap.has(name), parent)); } - parent.children = children; + return this.nodes.get(name)!; } } @@ -115,41 +128,78 @@ export class LoadmoreDatabase { styleUrl: 'tree-loadmore-example.css', providers: [LoadmoreDatabase], imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeLoadmoreExample { private _database = inject(LoadmoreDatabase); - dataSource = signal([]); + nodeMap = new Map(); + treeControl: FlatTreeControl; + treeFlattener: MatTreeFlattener; + // Flat tree data source + dataSource: MatTreeFlatDataSource; + + constructor() { + const _database = this._database; - childrenAccessor = (node: TreeNode) => node.children ?? []; + this.treeFlattener = new MatTreeFlattener( + this.transformer, + this.getLevel, + this.isExpandable, + this.getChildren, + ); - hasChild = (_: number, node: TreeNode) => node.expandable; + // TODO(#27626): Remove treeControl. Adopt either levelAccessor or childrenAccessor. + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); - isLoadMore = (_: number, node: TreeNode) => node.isLoadMore; + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - constructor() { - this.dataSource.set(this._database.initialize()); + _database.dataChange.subscribe(data => { + this.dataSource.data = data; + }); + + _database.initialize(); } - loadChildren(node: TreeNode) { + getChildren = (node: NestedNode): Observable => node.childrenChange; + + transformer = (node: NestedNode, level: number) => { + const existingNode = this.nodeMap.get(node.name); + + if (existingNode) { + return existingNode; + } + + const newNode = new FlatNode(node.name, level, node.hasChildren, node.parent, node.isLoadMore); + this.nodeMap.set(node.name, newNode); + return newNode; + }; + + getLevel = (node: FlatNode) => node.level; + + isExpandable = (node: FlatNode) => node.expandable; + + hasChild = (_: number, node: FlatNode) => node.expandable; + + isLoadMore = (_: number, node: FlatNode) => node.isLoadMore; + + loadChildren(node: FlatNode) { this._database.loadChildren(node.name, true); - // Trigger change detection by updating the signal - this.dataSource.set([...this.dataSource()]); } /** Load more nodes when clicking on "Load more" node. */ - loadOnClick(event: MouseEvent, node: TreeNode) { + loadOnClick(event: MouseEvent, node: FlatNode) { this._loadSiblings(event.target as HTMLElement, node); } - /** Load more nodes on keypress when focused on "Load more" node */ - loadOnKeypress(event: KeyboardEvent, node: TreeNode) { + /** Load more nodes on keyboardpress when focused on "Load more" node */ + loadOnKeypress(event: KeyboardEvent, node: FlatNode) { if (event.keyCode === ENTER || event.keyCode === SPACE) { this._loadSiblings(event.target as HTMLElement, node); } } - private _loadSiblings(nodeElement: HTMLElement, node: TreeNode) { + private _loadSiblings(nodeElement: HTMLElement, node: FlatNode) { if (node.parent) { // Store a reference to the sibling of the "Load More" node before it is removed from the DOM const previousSibling = nodeElement.previousElementSibling; @@ -157,14 +207,11 @@ export class TreeLoadmoreExample { // Synchronously load data. this._database.loadChildren(node.parent); - // Trigger change detection - this.dataSource.set([...this.dataSource()]); - - const focusDestination = previousSibling?.nextElementSibling || previousSibling; + const focusDesination = previousSibling?.nextElementSibling || previousSibling; - if (focusDestination) { + if (focusDesination) { // Restore focus. - (focusDestination as HTMLElement).focus(); + (focusDesination as HTMLElement).focus(); } } } diff --git a/src/components-examples/material/tree/tree-nested-child-accessor-overview/tree-nested-child-accessor-overview-example.ts b/src/components-examples/material/tree/tree-nested-child-accessor-overview/tree-nested-child-accessor-overview-example.ts index 83341e4557e6..6e3cc2371fa6 100644 --- a/src/components-examples/material/tree/tree-nested-child-accessor-overview/tree-nested-child-accessor-overview-example.ts +++ b/src/components-examples/material/tree/tree-nested-child-accessor-overview/tree-nested-child-accessor-overview-example.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatTreeModule} from '@angular/material/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -20,6 +20,7 @@ interface FoodNode { templateUrl: 'tree-nested-child-accessor-overview-example.html', styleUrl: 'tree-nested-child-accessor-overview-example.css', imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeNestedChildAccessorOverviewExample { childrenAccessor = (node: FoodNode) => node.children ?? []; diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html index ccc3c33c05ed..bd6eed8f9fa8 100644 --- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html +++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html @@ -1,4 +1,4 @@ - + @@ -6,20 +6,23 @@ {{node.name}} - +
    - {{node.name}}
    -
    +
    - \ No newline at end of file + diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts index cf85482be782..3500b3a3d034 100644 --- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts +++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.ts @@ -1,5 +1,6 @@ -import {Component} from '@angular/core'; -import {MatTreeModule} from '@angular/material/tree'; +import {NestedTreeControl} from '@angular/cdk/tree'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MatTreeNestedDataSource, MatTreeModule} from '@angular/material/tree'; import {MatIconModule} from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button'; @@ -20,11 +21,15 @@ interface FoodNode { templateUrl: 'tree-nested-overview-example.html', styleUrl: 'tree-nested-overview-example.css', imports: [MatTreeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeNestedOverviewExample { - childrenAccessor = (node: FoodNode) => node.children ?? []; + treeControl = new NestedTreeControl(node => node.children); + dataSource = new MatTreeNestedDataSource(); - dataSource = EXAMPLE_DATA; + constructor() { + this.dataSource.data = EXAMPLE_DATA; + } hasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0; } diff --git a/src/components-examples/package.json b/src/components-examples/package.json index e24b2187a042..c9c98b42e466 100644 --- a/src/components-examples/package.json +++ b/src/components-examples/package.json @@ -40,8 +40,7 @@ "@angular/cdk-experimental": "workspace:*", "@angular/material": "workspace:*", "@angular/material-experimental": "workspace:*", - "@angular/material-luxon-adapter": "workspace:*", - "@angular/material-date-fns-adapter": "workspace:*" + "@angular/material-luxon-adapter": "workspace:*" }, "dependencies": { "tslib": "^2.3.0" diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index faf459be208c..b18b11e22ea1 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -32,6 +32,7 @@ ng_project( "//src/dev-app/aria-listbox", "//src/dev-app/aria-menu", "//src/dev-app/aria-menubar", + "//src/dev-app/aria-select", "//src/dev-app/aria-tabs", "//src/dev-app/aria-toolbar", "//src/dev-app/aria-tree", diff --git a/src/dev-app/aria-accordion/accordion-demo.ts b/src/dev-app/aria-accordion/accordion-demo.ts index 112ec915f07e..75ff01621683 100644 --- a/src/dev-app/aria-accordion/accordion-demo.ts +++ b/src/dev-app/aria-accordion/accordion-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { AccordionConfigurableExample, AccordionDisabledExample, @@ -28,5 +28,6 @@ import { AccordionDisabledExample, ], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AccordionDemo {} diff --git a/src/dev-app/aria-autocomplete/autocomplete-demo.css b/src/dev-app/aria-autocomplete/autocomplete-demo.css index 4de68b6b42cf..e91c57a78bd9 100644 --- a/src/dev-app/aria-autocomplete/autocomplete-demo.css +++ b/src/dev-app/aria-autocomplete/autocomplete-demo.css @@ -1,16 +1,9 @@ :host { - display: flex; - flex-direction: column; - gap: 2rem; - padding: 1rem; -} - -.demo-row { display: flex; flex-wrap: wrap; - gap: 2rem; + gap: 10rem; } .example-container { - width: 300px; + width: 250px; } diff --git a/src/dev-app/aria-autocomplete/autocomplete-demo.html b/src/dev-app/aria-autocomplete/autocomplete-demo.html index a5adbec3be9c..a010a9c87585 100644 --- a/src/dev-app/aria-autocomplete/autocomplete-demo.html +++ b/src/dev-app/aria-autocomplete/autocomplete-demo.html @@ -1,23 +1,19 @@ -

    Autocomplete

    +
    +

    Auto select

    + +
    -
    -
    -

    Auto select

    - -
    +
    +

    Manual selection

    + +
    -
    -

    Manual selection

    - -
    +
    +

    Highlighted auto selection

    + +
    -
    -

    Highlighted auto selection

    - -
    - -
    -

    Disabled autocomplete

    - -
    -
    \ No newline at end of file +
    +

    Disabled autocomplete

    + +
    diff --git a/src/dev-app/aria-autocomplete/autocomplete-demo.ts b/src/dev-app/aria-autocomplete/autocomplete-demo.ts index f8d18f85a291..38179f50296e 100644 --- a/src/dev-app/aria-autocomplete/autocomplete-demo.ts +++ b/src/dev-app/aria-autocomplete/autocomplete-demo.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import { AutocompleteAutoSelectExample, AutocompleteManualExample, @@ -16,5 +16,6 @@ import { AutocompleteHighlightExample, AutocompleteDisabledExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteDemo {} diff --git a/src/dev-app/aria-combobox/combobox-demo.css b/src/dev-app/aria-combobox/combobox-demo.css index 4083f0414ecc..607c068d07ef 100644 --- a/src/dev-app/aria-combobox/combobox-demo.css +++ b/src/dev-app/aria-combobox/combobox-demo.css @@ -20,6 +20,3 @@ h2 { h3 { font-size: 1rem; } -.demo-combobox { - padding-bottom: 300px; -} diff --git a/src/dev-app/aria-combobox/combobox-demo.html b/src/dev-app/aria-combobox/combobox-demo.html index 6661f8135f8b..01048429e8d6 100644 --- a/src/dev-app/aria-combobox/combobox-demo.html +++ b/src/dev-app/aria-combobox/combobox-demo.html @@ -1,35 +1,26 @@ -
    +

    Listbox autocomplete examples

    Combobox with manual filtering

    - +
    - +
    -

    Combobox with auto-select

    +

    Combobox with auto-select filtering

    +
    -

    Combobox with highlight

    +

    Combobox with highlight filtering

    -

    Combobox with disabled

    +

    Disabled combobox

    - -
    -

    Combobox with readonly

    - -
    - -
    -

    Combobox with hard disabled

    - -

    Tree autocomplete examples

    @@ -37,11 +28,11 @@

    Tree autocomplete examples

    Combobox with tree popup and manual filtering

    - +
    -

    Combobox with tree popup and auto-select

    +

    Combobox with tree popup and auto-select filtering

    @@ -51,48 +42,31 @@

    Combobox with tree popup and highlight filtering

    -

    Combobox select examples

    +

    Select examples

    -

    Combobox with select

    - +

    Readonly Combobox

    +
    -

    Combobox with Multi-Select

    +

    Readonly Multiselect Combobox

    -

    Combobox with Disabled

    +

    Disabled Readonly Combobox

    -

    Combobox with Dialog Popup

    - -
    +

    Combobox with dialog popup

    + +
    -

    Combobox with Dialog Popup

    +

    Combobox with dialog popup

    -
    -

    Editable Combobox with Multi-Select Dialog

    - -
    - -

    Combobox Grid Examples

    - -
    -
    -

    Combobox with Grid

    - -
    -
    -

    Combobox with Datepicker Grid

    - -
    -
    \ No newline at end of file diff --git a/src/dev-app/aria-combobox/combobox-demo.ts b/src/dev-app/aria-combobox/combobox-demo.ts index b8d667c6147f..efb033b6a777 100644 --- a/src/dev-app/aria-combobox/combobox-demo.ts +++ b/src/dev-app/aria-combobox/combobox-demo.ts @@ -6,46 +6,37 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; import { - ComboboxListboxExample, - ComboboxTreeExample, - ComboboxSelectExample, - ComboboxGridExample, - ComboboxDatepickerExample, + ComboboxDialogExample, ComboboxAutoSelectExample, ComboboxHighlightExample, + ComboboxManualExample, ComboboxDisabledExample, ComboboxReadonlyExample, - ComboboxHardDisabledExample, - ComboboxReadonlyDisabledExample, ComboboxReadonlyMultiselectExample, - ComboboxDialogExample, + ComboboxReadonlyDisabledExample, ComboboxTreeAutoSelectExample, ComboboxTreeHighlightExample, - ComboboxMultiselectDialogExample, + ComboboxTreeManualExample, } from '@angular/components-examples/aria/combobox'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ templateUrl: 'combobox-demo.html', styleUrl: 'combobox-demo.css', imports: [ - ComboboxListboxExample, - ComboboxTreeExample, - ComboboxSelectExample, - ComboboxGridExample, - ComboboxDatepickerExample, + ComboboxDialogExample, + ComboboxManualExample, ComboboxAutoSelectExample, ComboboxHighlightExample, ComboboxDisabledExample, ComboboxReadonlyExample, - ComboboxHardDisabledExample, - ComboboxReadonlyDisabledExample, ComboboxReadonlyMultiselectExample, - ComboboxDialogExample, + ComboboxReadonlyDisabledExample, + ComboboxTreeManualExample, ComboboxTreeAutoSelectExample, ComboboxTreeHighlightExample, - ComboboxMultiselectDialogExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ComboboxDemo {} diff --git a/src/dev-app/aria-grid/grid-demo.ts b/src/dev-app/aria-grid/grid-demo.ts index 79011eb15814..6bebfb9a10b4 100644 --- a/src/dev-app/aria-grid/grid-demo.ts +++ b/src/dev-app/aria-grid/grid-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { GridConfigurableExample, GridPillListExample, @@ -19,5 +19,6 @@ import { imports: [GridConfigurableExample, GridPillListExample, GridCalendarExample, GridTableExample], styleUrl: 'grid-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class GridDemo {} diff --git a/src/dev-app/aria-listbox/listbox-demo.ts b/src/dev-app/aria-listbox/listbox-demo.ts index 04d6d604b962..d5c09fc66701 100644 --- a/src/dev-app/aria-listbox/listbox-demo.ts +++ b/src/dev-app/aria-listbox/listbox-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { ListboxConfigurableExample, ListboxSingleSelectExample, @@ -40,5 +40,6 @@ import { ], styleUrl: 'listbox-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ListboxDemo {} diff --git a/src/dev-app/aria-menu/menu-demo.html b/src/dev-app/aria-menu/menu-demo.html index ead6faddd1a2..e17b06194775 100644 --- a/src/dev-app/aria-menu/menu-demo.html +++ b/src/dev-app/aria-menu/menu-demo.html @@ -24,10 +24,5 @@

    Disabled Standalone Menu Example

    Context Menu Example

    - -
    -

    Menu CDK Overlay Example

    - -
    diff --git a/src/dev-app/aria-menu/menu-demo.ts b/src/dev-app/aria-menu/menu-demo.ts index 00c962aa88c1..1f2ef3b4ae29 100644 --- a/src/dev-app/aria-menu/menu-demo.ts +++ b/src/dev-app/aria-menu/menu-demo.ts @@ -6,27 +6,26 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { MenuContextExample, MenuTriggerExample, MenuStandaloneExample, MenuStandaloneDisabledExample, MenuTriggerDisabledExample, - MenuCdkOverlayExample, } from '@angular/components-examples/aria/menu'; @Component({ templateUrl: 'menu-demo.html', styleUrl: 'menu-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MenuContextExample, MenuTriggerExample, MenuTriggerDisabledExample, MenuStandaloneExample, MenuStandaloneDisabledExample, - MenuCdkOverlayExample, ], }) export class MenuDemo {} diff --git a/src/dev-app/aria-menubar/menubar-demo.ts b/src/dev-app/aria-menubar/menubar-demo.ts index f2ddfe248f01..aec11c487eaf 100644 --- a/src/dev-app/aria-menubar/menubar-demo.ts +++ b/src/dev-app/aria-menubar/menubar-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { MenuBarExample, MenuBarRTLExample, @@ -17,6 +17,7 @@ import { templateUrl: 'menubar-demo.html', styleUrl: 'menubar-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MenuBarExample, MenuBarRTLExample, MenuBarDisabledExample], }) export class MenubarDemo {} diff --git a/src/dev-app/aria-select/BUILD.bazel b/src/dev-app/aria-select/BUILD.bazel new file mode 100644 index 000000000000..8e65d8720b89 --- /dev/null +++ b/src/dev-app/aria-select/BUILD.bazel @@ -0,0 +1,16 @@ +load("//tools:defaults.bzl", "ng_project") + +package(default_visibility = ["//visibility:public"]) + +ng_project( + name = "aria-select", + srcs = glob(["**/*.ts"]), + assets = [ + "select-demo.html", + ":select-demo.css", + ], + deps = [ + "//:node_modules/@angular/core", + "//src/components-examples/aria/select", + ], +) diff --git a/src/dev-app/aria-select/select-demo.css b/src/dev-app/aria-select/select-demo.css new file mode 100644 index 000000000000..e91c57a78bd9 --- /dev/null +++ b/src/dev-app/aria-select/select-demo.css @@ -0,0 +1,9 @@ +:host { + display: flex; + flex-wrap: wrap; + gap: 10rem; +} + +.example-container { + width: 250px; +} diff --git a/src/dev-app/aria-select/select-demo.html b/src/dev-app/aria-select/select-demo.html new file mode 100644 index 000000000000..b3c95c298274 --- /dev/null +++ b/src/dev-app/aria-select/select-demo.html @@ -0,0 +1,14 @@ +
    +

    Select Example

    + +
    + +
    +

    Multiselect Example

    + +
    + +
    +

    Disabled Example

    + +
    diff --git a/src/dev-app/aria-select/select-demo.ts b/src/dev-app/aria-select/select-demo.ts new file mode 100644 index 000000000000..ef1e85d8c332 --- /dev/null +++ b/src/dev-app/aria-select/select-demo.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import { + SelectDisabledExample, + SelectMultiExample, + SelectExample, +} from '@angular/components-examples/aria/select'; + +@Component({ + templateUrl: 'select-demo.html', + styleUrl: 'select-demo.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SelectDisabledExample, SelectMultiExample, SelectExample], +}) +export class SelectDemo {} diff --git a/src/dev-app/aria-tabs/tabs-demo.html b/src/dev-app/aria-tabs/tabs-demo.html index f400eeaec13b..3fae6f69744d 100644 --- a/src/dev-app/aria-tabs/tabs-demo.html +++ b/src/dev-app/aria-tabs/tabs-demo.html @@ -38,11 +38,6 @@

    Disabled Tabs are Skipped

    Disabled

    - -
    -

    Scrollable Tabs

    - -
    diff --git a/src/dev-app/aria-tabs/tabs-demo.ts b/src/dev-app/aria-tabs/tabs-demo.ts index cbd9d8f35156..a1cb0705920e 100644 --- a/src/dev-app/aria-tabs/tabs-demo.ts +++ b/src/dev-app/aria-tabs/tabs-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import { TabsConfigurableExample, TabsExplicitSelectionExample, @@ -17,7 +17,6 @@ import { TabsDisabledFocusableExample, TabsDisabledSkippedExample, TabsDisabledExample, - TabsScrollableExample, } from '@angular/components-examples/aria/tabs'; @Component({ @@ -34,7 +33,7 @@ import { TabsDisabledFocusableExample, TabsDisabledSkippedExample, TabsDisabledExample, - TabsScrollableExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TabsDemo {} diff --git a/src/dev-app/aria-toolbar/toolbar-demo.css b/src/dev-app/aria-toolbar/toolbar-demo.css index f9682baa7ca7..7164c58accca 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.css +++ b/src/dev-app/aria-toolbar/toolbar-demo.css @@ -22,5 +22,3 @@ h4 { .example-toolbar-container { width: 100%; } - - diff --git a/src/dev-app/aria-toolbar/toolbar-demo.html b/src/dev-app/aria-toolbar/toolbar-demo.html index b6e9553457de..3a309bf5e0ad 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.html +++ b/src/dev-app/aria-toolbar/toolbar-demo.html @@ -1,23 +1,24 @@ -

    Toolbar Examples with Combobox

    -
    -
    -

    Toolbar Basic Horizontal

    - -
    -
    -

    Toolbar Basic Vertical

    - -
    -
    -

    Toolbar with Hard Disabled Items

    - -
    -
    -

    Toolbar RTL

    - -
    -
    -

    Configurable CDK Toolbar

    - +
    +
    +
    +

    Toolbar Basic Horizontal

    + +
    +
    +

    Toolbar Basic Vertical

    + +
    +
    +

    Toolbar with Hard Disabled Items

    + +
    +
    +

    Toolbar RTL

    + +
    +
    +

    Configurable CDK Toolbar

    + +
    diff --git a/src/dev-app/aria-toolbar/toolbar-demo.ts b/src/dev-app/aria-toolbar/toolbar-demo.ts index 08910d10af16..7f133355b95f 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.ts +++ b/src/dev-app/aria-toolbar/toolbar-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { ToolbarBasicHorizontalExample, ToolbarBasicVerticalExample, @@ -26,5 +26,6 @@ import { ], styleUrl: './toolbar-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ToolbarDemo {} diff --git a/src/dev-app/aria-tree/tree-demo.ts b/src/dev-app/aria-tree/tree-demo.ts index a309eaf412f5..bdd42762f58c 100644 --- a/src/dev-app/aria-tree/tree-demo.ts +++ b/src/dev-app/aria-tree/tree-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { TreeConfigurableExample, TreeActiveDescendantExample, @@ -38,5 +38,6 @@ import { ], styleUrl: 'tree-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeDemo {} diff --git a/src/dev-app/autocomplete/autocomplete-demo.ts b/src/dev-app/autocomplete/autocomplete-demo.ts index 72d8cffed1c9..f191e9b47223 100644 --- a/src/dev-app/autocomplete/autocomplete-demo.ts +++ b/src/dev-app/autocomplete/autocomplete-demo.ts @@ -7,7 +7,7 @@ */ import {JsonPipe} from '@angular/common'; -import {Component, inject, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, ViewChild} from '@angular/core'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatButtonModule} from '@angular/material/button'; @@ -44,6 +44,7 @@ type DisableStateOption = 'none' | 'first-middle-last' | 'all'; MatInputModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteDemo { stateCtrl = new FormControl(); @@ -244,6 +245,7 @@ export class AutocompleteDemo { } `, imports: [FormsModule, MatAutocompleteModule, MatButtonModule, MatInputModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteDemoExampleDialog { dialogRef = inject>(MatDialogRef); diff --git a/src/dev-app/badge/badge-demo.ts b/src/dev-app/badge/badge-demo.ts index 39ca0a288897..a27e2fac6866 100644 --- a/src/dev-app/badge/badge-demo.ts +++ b/src/dev-app/badge/badge-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatBadgeModule} from '@angular/material/badge'; import {MatButtonModule} from '@angular/material/button'; @@ -17,6 +17,7 @@ import {MatIconModule} from '@angular/material/icon'; templateUrl: 'badge-demo.html', styleUrl: 'badge-demo.css', imports: [FormsModule, MatBadgeModule, MatButtonModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BadgeDemo { visible = true; diff --git a/src/dev-app/baseline/baseline-demo.ts b/src/dev-app/baseline/baseline-demo.ts index a67afc03e0ce..29751e97f798 100644 --- a/src/dev-app/baseline/baseline-demo.ts +++ b/src/dev-app/baseline/baseline-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatCardModule} from '@angular/material/card'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -28,6 +28,7 @@ import {MatToolbarModule} from '@angular/material/toolbar'; MatSelectModule, MatToolbarModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BaselineDemo { name = ''; diff --git a/src/dev-app/bottom-sheet/bottom-sheet-demo.ts b/src/dev-app/bottom-sheet/bottom-sheet-demo.ts index 1b8260c15de3..0af5c68c8367 100644 --- a/src/dev-app/bottom-sheet/bottom-sheet-demo.ts +++ b/src/dev-app/bottom-sheet/bottom-sheet-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, TemplateRef, ViewChild, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, TemplateRef, ViewChild, inject} from '@angular/core'; import {FormsModule} from '@angular/forms'; import { MatBottomSheet, @@ -41,6 +41,7 @@ const defaultConfig = new MatBottomSheetConfig(); MatSelectModule, MatListModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BottomSheetDemo { private _bottomSheet = inject(MatBottomSheet); @@ -76,6 +77,7 @@ export class BottomSheetDemo { `, imports: [MatListModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExampleBottomSheet { private _bottomSheet = inject(MatBottomSheetRef); diff --git a/src/dev-app/button-toggle/button-toggle-demo.ts b/src/dev-app/button-toggle/button-toggle-demo.ts index 0bd9b5a7e6ad..2d48fcbaadb4 100644 --- a/src/dev-app/button-toggle/button-toggle-demo.ts +++ b/src/dev-app/button-toggle/button-toggle-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonToggleModule} from '@angular/material/button-toggle'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -17,6 +17,7 @@ import {MatIconModule} from '@angular/material/icon'; templateUrl: 'button-toggle-demo.html', styleUrl: 'button-toggle-demo.css', imports: [FormsModule, MatButtonToggleModule, MatCheckboxModule, MatIconModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonToggleDemo { isVertical = false; diff --git a/src/dev-app/button/BUILD.bazel b/src/dev-app/button/BUILD.bazel index fe4c5d24315a..c94c68c9b705 100644 --- a/src/dev-app/button/BUILD.bazel +++ b/src/dev-app/button/BUILD.bazel @@ -15,7 +15,6 @@ ng_project( "//src/material/button", "//src/material/checkbox", "//src/material/icon", - "//src/material/progress-spinner", "//src/material/tooltip", ], ) diff --git a/src/dev-app/button/button-demo.html b/src/dev-app/button/button-demo.html index 7e70494a204c..2d96f2740820 100644 --- a/src/dev-app/button/button-demo.html +++ b/src/dev-app/button/button-demo.html @@ -23,20 +23,20 @@

    Buttons

    [matButton]="appearance" disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText" - > - {{appearance}} - + [matTooltip]="tooltipText">{{appearance}} } - + [matTooltip]="tooltipText">Search - -
    - @for (appearance of appearances; track $index) { - - } - - - -
    @for (appearance of appearances; track $index) { -

    - {{appearance}} Appearance -

    +

    {{appearance}} Appearance

    @@ -225,10 +149,7 @@

    [matButton]="appearance" disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText" - > - disabled - + [matTooltip]="tooltipText">disabled -

    } @@ -270,20 +180,9 @@

    Icon Buttons

    matIconButton disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText" - > + [matTooltip]="tooltipText"> visibility -

    Icon Button Anchors

    @@ -305,8 +204,7 @@

    Icon Button Anchors

    matIconButton disabled [disabledInteractive]="disabledInteractive" - [matTooltip]="tooltipText" - > + [matTooltip]="tooltipText"> visibility
    @@ -325,19 +223,13 @@

    FABs

    - -

    Mini FABs

    @@ -354,22 +246,11 @@

    Mini FABs

    - @@ -380,106 +261,37 @@

    Interaction/State

    isDisabled: {{isDisabled}}

    Button 1 as been clicked {{clickCounter}} times

    - Allow disabled button interactivity + Allow disabled button interactivity

    All disabled

    -

    - All showProgress -

    - - - + Button 3 - - - -
    diff --git a/src/dev-app/button/button-demo.ts b/src/dev-app/button/button-demo.ts index 1a93d6b928bc..b8b8fa4b42f3 100644 --- a/src/dev-app/button/button-demo.ts +++ b/src/dev-app/button/button-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import { MatAnchor, @@ -21,7 +21,6 @@ import { } from '@angular/material/button'; import {MatCheckbox} from '@angular/material/checkbox'; import {MatIcon} from '@angular/material/icon'; -import {MatProgressSpinner} from '@angular/material/progress-spinner'; import {MatTooltip} from '@angular/material/tooltip'; @Component({ @@ -29,24 +28,23 @@ import {MatTooltip} from '@angular/material/tooltip'; templateUrl: 'button-demo.html', styleUrl: 'button-demo.css', imports: [ - FormsModule, - MatAnchor, MatButton, - MatCheckbox, - MatFabAnchor, + MatAnchor, MatFabButton, - MatIcon, - MatIconAnchor, - MatIconButton, - MatMiniFabAnchor, + MatFabAnchor, MatMiniFabButton, - MatProgressSpinner, + MatMiniFabAnchor, + MatIconButton, + MatIconAnchor, + MatIcon, MatTooltip, + MatCheckbox, + FormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ButtonDemo { isDisabled = false; - showProgress = false; clickCounter = 0; toggleDisable = false; tooltipText = 'This is a button tooltip!'; diff --git a/src/dev-app/card/card-demo.ts b/src/dev-app/card/card-demo.ts index 6a63ea5afb65..7202b53a81e1 100644 --- a/src/dev-app/card/card-demo.ts +++ b/src/dev-app/card/card-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCardAppearance, MatCardModule} from '@angular/material/card'; @@ -18,6 +18,7 @@ import {MatRadioModule} from '@angular/material/radio'; styleUrl: 'card-demo.css', encapsulation: ViewEncapsulation.None, imports: [MatCardModule, MatButtonModule, MatRadioModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CardDemo { appearance: MatCardAppearance = 'raised'; diff --git a/src/dev-app/cdk-dialog/dialog-demo.ts b/src/dev-app/cdk-dialog/dialog-demo.ts index 6e8cb0faced3..a08826b9aca2 100644 --- a/src/dev-app/cdk-dialog/dialog-demo.ts +++ b/src/dev-app/cdk-dialog/dialog-demo.ts @@ -8,6 +8,7 @@ import {DIALOG_DATA, Dialog, DialogConfig, DialogModule, DialogRef} from '@angular/cdk/dialog'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, TemplateRef, @@ -25,6 +26,7 @@ const defaultDialogConfig = new DialogConfig(); styleUrl: 'dialog-demo.css', encapsulation: ViewEncapsulation.None, imports: [DialogModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogDemo { dialog = inject(Dialog); @@ -106,6 +108,7 @@ export class DialogDemo { opacity: 0; } `, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class JazzDialog { dialogRef = inject>(DialogRef); diff --git a/src/dev-app/cdk-listbox/cdk-listbox-demo.ts b/src/dev-app/cdk-listbox/cdk-listbox-demo.ts index c7090634ec99..6f74cf03487a 100644 --- a/src/dev-app/cdk-listbox/cdk-listbox-demo.ts +++ b/src/dev-app/cdk-listbox/cdk-listbox-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import { CdkListboxActivedescendantExample, CdkListboxCompareWithExample, @@ -38,5 +38,6 @@ import { CdkListboxTemplateFormsExample, CdkListboxValueBindingExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkListboxDemo {} diff --git a/src/dev-app/cdk-menu/cdk-menu-demo.ts b/src/dev-app/cdk-menu/cdk-menu-demo.ts index cd12789a388b..ba2f7aa5c799 100644 --- a/src/dev-app/cdk-menu/cdk-menu-demo.ts +++ b/src/dev-app/cdk-menu/cdk-menu-demo.ts @@ -16,7 +16,7 @@ import { CdkMenuStandaloneMenuExample, CdkMenuStandaloneStatefulMenuExample, } from '@angular/components-examples/cdk/menu'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ templateUrl: 'cdk-menu-demo.html', @@ -30,6 +30,7 @@ import {Component} from '@angular/core'; CdkMenuContextExample, CdkMenuNestedContextExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkMenuDemo { customPosition = [ diff --git a/src/dev-app/checkbox/checkbox-demo.ts b/src/dev-app/checkbox/checkbox-demo.ts index ef9bc4c548c0..adb577d891ba 100644 --- a/src/dev-app/checkbox/checkbox-demo.ts +++ b/src/dev-app/checkbox/checkbox-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, Directive} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Directive} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckboxModule} from '@angular/material/checkbox'; import {MatPseudoCheckboxModule, ThemePalette} from '@angular/material/core'; @@ -41,6 +41,7 @@ export class ClickActionCheck {} `, templateUrl: 'nested-checklist.html', imports: [MatCheckboxModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatCheckboxDemoNestedChecklist { tasks: Task[] = [ @@ -102,6 +103,7 @@ export class MatCheckboxDemoNestedChecklist { ClickActionCheck, MatTooltip, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CheckboxDemo { isIndeterminate = false; diff --git a/src/dev-app/chips/chips-demo.ts b/src/dev-app/chips/chips-demo.ts index a9e51bf46005..740bee4b97c1 100644 --- a/src/dev-app/chips/chips-demo.ts +++ b/src/dev-app/chips/chips-demo.ts @@ -8,7 +8,7 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; import {COMMA, ENTER} from '@angular/cdk/keycodes'; -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCardModule} from '@angular/material/card'; @@ -42,6 +42,7 @@ export interface DemoColor { MatIconModule, ReactiveFormsModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ChipsDemo { visible = true; diff --git a/src/dev-app/clipboard/clipboard-demo.ts b/src/dev-app/clipboard/clipboard-demo.ts index 7daea1580baf..e43efab13c1d 100644 --- a/src/dev-app/clipboard/clipboard-demo.ts +++ b/src/dev-app/clipboard/clipboard-demo.ts @@ -7,7 +7,7 @@ */ import {Clipboard, ClipboardModule} from '@angular/cdk/clipboard'; -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {FormsModule} from '@angular/forms'; @Component({ @@ -15,6 +15,7 @@ import {FormsModule} from '@angular/forms'; styleUrl: 'clipboard-demo.css', templateUrl: 'clipboard-demo.html', imports: [ClipboardModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ClipboardDemo { private _clipboard = inject(Clipboard); diff --git a/src/dev-app/column-resize/column-resize-home.ts b/src/dev-app/column-resize/column-resize-home.ts index 532136c82572..c9f36480153a 100644 --- a/src/dev-app/column-resize/column-resize-home.ts +++ b/src/dev-app/column-resize/column-resize-home.ts @@ -11,7 +11,7 @@ import { DefaultEnabledColumnResizeFlexExample, OptInColumnResizeExample, } from '@angular/components-examples/material-experimental/column-resize'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatExpansionModule} from '@angular/material/expansion'; @Component({ @@ -22,5 +22,6 @@ import {MatExpansionModule} from '@angular/material/expansion'; DefaultEnabledColumnResizeFlexExample, OptInColumnResizeExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ColumnResizeHome {} diff --git a/src/dev-app/connected-overlay/connected-overlay-demo.ts b/src/dev-app/connected-overlay/connected-overlay-demo.ts index 8ef0e8ba308e..c1e78403dec0 100644 --- a/src/dev-app/connected-overlay/connected-overlay-demo.ts +++ b/src/dev-app/connected-overlay/connected-overlay-demo.ts @@ -20,6 +20,7 @@ import { import {TemplatePortal} from '@angular/cdk/portal'; import {CdkOverlayBasicExample} from '@angular/components-examples/cdk/overlay'; import { + ChangeDetectionStrategy, Component, Injector, TemplateRef, @@ -46,6 +47,7 @@ import {MatRadioModule} from '@angular/material/radio'; MatRadioModule, OverlayModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ConnectedOverlayDemo { private _injector = inject(Injector); diff --git a/src/dev-app/datepicker/datepicker-demo.ts b/src/dev-app/datepicker/datepicker-demo.ts index ec803020f4fe..e19d0826fd16 100644 --- a/src/dev-app/datepicker/datepicker-demo.ts +++ b/src/dev-app/datepicker/datepicker-demo.ts @@ -7,10 +7,11 @@ */ import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, - Service, + Injectable, OnDestroy, ViewChild, ViewEncapsulation, @@ -38,7 +39,7 @@ import {Subject} from 'rxjs'; import {takeUntil} from 'rxjs/operators'; /** Range selection strategy that preserves the current range. */ -@Service({autoProvided: false}) +@Injectable() export class PreserveRangeStrategy implements MatDateRangeSelectionStrategy { private _dateAdapter = inject>(DateAdapter); @@ -100,6 +101,7 @@ export class CustomRangeStrategy {} selector: 'custom-header', templateUrl: 'custom-header.html', styleUrl: 'custom-header.css', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconModule, MatButtonModule], }) export class CustomHeader implements OnDestroy { @@ -150,6 +152,7 @@ export class CustomHeader implements OnDestroy { `, imports: [MatDatepickerModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CustomHeaderNgContent { private _dateAdapter = inject>(DateAdapter); @@ -170,6 +173,7 @@ export class CustomHeaderNgContent { templateUrl: 'datepicker-demo.html', styleUrl: 'datepicker-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ JsonPipe, FormsModule, diff --git a/src/dev-app/dev-app.ts b/src/dev-app/dev-app.ts index 1fec463aaf75..b68f38276ad4 100644 --- a/src/dev-app/dev-app.ts +++ b/src/dev-app/dev-app.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {RouterModule} from '@angular/router'; import {DevAppLayout} from './dev-app/dev-app-layout'; @@ -16,5 +16,6 @@ import {DevAppLayout} from './dev-app/dev-app-layout'; template: '', encapsulation: ViewEncapsulation.None, imports: [DevAppLayout, RouterModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DevApp {} diff --git a/src/dev-app/dev-app/dev-app-404.ts b/src/dev-app/dev-app/dev-app-404.ts index a5018ed9fc07..35ef36b02ba8 100644 --- a/src/dev-app/dev-app/dev-app-404.ts +++ b/src/dev-app/dev-app/dev-app-404.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {RouterModule} from '@angular/router'; @@ -18,5 +18,6 @@ import {RouterModule} from '@angular/router'; `, host: {'class': 'mat-typography'}, imports: [MatButtonModule, RouterModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DevApp404 {} diff --git a/src/dev-app/dev-app/dev-app-directionality.ts b/src/dev-app/dev-app/dev-app-directionality.ts index fddd2b40b664..64b833d93455 100644 --- a/src/dev-app/dev-app/dev-app-directionality.ts +++ b/src/dev-app/dev-app/dev-app-directionality.ts @@ -7,9 +7,9 @@ */ import {Direction, Directionality} from '@angular/cdk/bidi'; -import {EventEmitter, Service, OnDestroy, signal} from '@angular/core'; +import {EventEmitter, Injectable, OnDestroy, signal} from '@angular/core'; -@Service({autoProvided: false}) +@Injectable() export class DevAppDirectionality implements Directionality, OnDestroy { readonly change = new EventEmitter(); diff --git a/src/dev-app/dev-app/dev-app-home.ts b/src/dev-app/dev-app/dev-app-home.ts index db8d7408d0db..f309ddc10656 100644 --- a/src/dev-app/dev-app/dev-app-home.ts +++ b/src/dev-app/dev-app/dev-app-home.ts @@ -7,12 +7,13 @@ */ /** Home component which includes a welcome message for the dev-app. */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ template: `

    Welcome to the development demos for Angular Material!

    Open the sidenav to select a demo.

    `, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DevAppHome {} diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index 436ccec8b721..8e552a3d0727 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -8,6 +8,7 @@ import {Direction, Directionality} from '@angular/cdk/bidi'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -45,6 +46,7 @@ import {DevAppDirectionality} from './dev-app-directionality'; MatTooltipModule, RouterModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DevAppLayout { private _element = inject>(ElementRef); @@ -61,12 +63,13 @@ export class DevAppLayout { {name: 'Examples', route: '/examples'}, {name: 'CDK Dialog', route: '/cdk-dialog'}, {name: 'Aria Accordion', route: '/aria-accordion'}, - {name: 'Aria Autocomplete', route: '/aria-autocomplete'}, {name: 'Aria Combobox', route: '/aria-combobox'}, + {name: 'Aria Autocomplete', route: '/aria-autocomplete'}, {name: 'Aria Grid', route: '/aria-grid'}, {name: 'Aria Listbox', route: '/aria-listbox'}, {name: 'Aria Menu', route: '/aria-menu'}, {name: 'Aria Menubar', route: '/aria-menubar'}, + {name: 'Aria Select', route: '/aria-select'}, {name: 'Aria Tabs', route: '/aria-tabs'}, {name: 'Aria Toolbar', route: '/aria-toolbar'}, {name: 'Aria Tree', route: '/aria-tree'}, diff --git a/src/dev-app/dev-app/ripple-options.ts b/src/dev-app/dev-app/ripple-options.ts index 047a1d8839d9..ac95e3c00d33 100644 --- a/src/dev-app/dev-app/ripple-options.ts +++ b/src/dev-app/dev-app/ripple-options.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {RippleGlobalOptions} from '@angular/material/core'; /** * Global ripple options for the dev-app. The ripple options are used as a class * so that the global options can be changed at runtime. */ -@Service() +@Injectable({providedIn: 'root'}) export class DevAppRippleOptions implements RippleGlobalOptions { /** Whether ripples should be disabled */ disabled: boolean = false; diff --git a/src/dev-app/dialog/dialog-demo.ts b/src/dev-app/dialog/dialog-demo.ts index 768f1d56b1b1..cd41ef6a330c 100644 --- a/src/dev-app/dialog/dialog-demo.ts +++ b/src/dev-app/dialog/dialog-demo.ts @@ -9,6 +9,7 @@ import {DragDropModule} from '@angular/cdk/drag-drop'; import {JsonPipe} from '@angular/common'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, TemplateRef, @@ -53,6 +54,7 @@ import {MatSelectModule} from '@angular/material/select'; MatSelectModule, JsonPipe, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DialogDemo { dialog = inject(MatDialog); @@ -176,6 +178,7 @@ export class DialogDemo { encapsulation: ViewEncapsulation.None, styles: `.hidden-dialog { opacity: 0; }`, imports: [DragDropModule, MatInputModule, MatSelectModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class JazzDialog { dialogRef = inject>(MatDialogRef); @@ -250,6 +253,7 @@ export class JazzDialog { `, imports: [MatButtonModule, MatDialogTitle, MatDialogContent, MatDialogClose, MatDialogActions], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ContentElementDialog { dialog = inject(MatDialog); @@ -285,5 +289,6 @@ export class ContentElementDialog { `, imports: [MatButtonModule, MatDialogTitle, MatDialogContent, MatDialogClose, MatDialogActions], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class IFrameDialog {} diff --git a/src/dev-app/drag-drop/drag-drop-demo.ts b/src/dev-app/drag-drop/drag-drop-demo.ts index fae3895bd5e3..d4bd63ea6601 100644 --- a/src/dev-app/drag-drop/drag-drop-demo.ts +++ b/src/dev-app/drag-drop/drag-drop-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation, inject} from '@angular/core'; +import {Component, ViewEncapsulation, ChangeDetectionStrategy, inject} from '@angular/core'; import {MatIconModule, MatIconRegistry} from '@angular/material/icon'; import {DomSanitizer} from '@angular/platform-browser'; import { @@ -28,6 +28,7 @@ import {MatCheckbox} from '@angular/material/checkbox'; templateUrl: 'drag-drop-demo.html', styleUrl: 'drag-drop-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ DragDropModule, FormsModule, diff --git a/src/dev-app/drawer/drawer-demo.ts b/src/dev-app/drawer/drawer-demo.ts index c7ada3b95266..c14bf9ad438b 100644 --- a/src/dev-app/drawer/drawer-demo.ts +++ b/src/dev-app/drawer/drawer-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatListModule} from '@angular/material/list'; import {MatSidenavModule} from '@angular/material/sidenav'; @@ -16,6 +16,7 @@ import {MatSidenavModule} from '@angular/material/sidenav'; templateUrl: 'drawer-demo.html', styleUrl: 'drawer-demo.css', imports: [MatButtonModule, MatListModule, MatSidenavModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DrawerDemo { invert = false; diff --git a/src/dev-app/example/example-list.ts b/src/dev-app/example/example-list.ts index f3d13e9ecf11..a0f578886992 100644 --- a/src/dev-app/example/example-list.ts +++ b/src/dev-app/example/example-list.ts @@ -8,7 +8,7 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {EXAMPLE_COMPONENTS} from '@angular/components-examples'; -import {Component, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {MatExpansionModule} from '@angular/material/expansion'; import {Example} from './example'; @@ -56,6 +56,7 @@ import {Example} from './example'; font-size: 12px; } `, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExampleList { /** Type of examples being displayed. */ diff --git a/src/dev-app/example/example.ts b/src/dev-app/example/example.ts index 4465d9be3b81..0eb8fdafe385 100644 --- a/src/dev-app/example/example.ts +++ b/src/dev-app/example/example.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, @@ -55,6 +56,7 @@ import {loadExample} from '@angular/components-examples/private'; white-space: pre; } `, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class Example implements OnInit { private _injector = inject(Injector); diff --git a/src/dev-app/examples-page/examples-page.ts b/src/dev-app/examples-page/examples-page.ts index 6eaa97e5a658..8c9ed23fb917 100644 --- a/src/dev-app/examples-page/examples-page.ts +++ b/src/dev-app/examples-page/examples-page.ts @@ -7,13 +7,14 @@ */ import {EXAMPLE_COMPONENTS} from '@angular/components-examples'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {ExampleList} from '../example/example-list'; /** Renders all material examples listed in the generated EXAMPLE_COMPONENTS. */ @Component({ template: ``, imports: [ExampleList], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExamplesPage { examples = Object.keys(EXAMPLE_COMPONENTS); diff --git a/src/dev-app/expansion/expansion-demo.ts b/src/dev-app/expansion/expansion-demo.ts index 0d7ea6387362..72a92dd25531 100644 --- a/src/dev-app/expansion/expansion-demo.ts +++ b/src/dev-app/expansion/expansion-demo.ts @@ -7,7 +7,7 @@ */ import {CdkAccordionModule} from '@angular/cdk/accordion'; -import {Component, ViewChild} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -37,6 +37,7 @@ import {MatSlideToggleModule} from '@angular/material/slide-toggle'; MatRadioModule, MatSlideToggleModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExpansionDemo { @ViewChild(MatAccordion) accordion!: MatAccordion; diff --git a/src/dev-app/focus-origin/focus-origin-demo.ts b/src/dev-app/focus-origin/focus-origin-demo.ts index 1d4b3bb1da8d..ff69fdd44706 100644 --- a/src/dev-app/focus-origin/focus-origin-demo.ts +++ b/src/dev-app/focus-origin/focus-origin-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; import {A11yModule, FocusMonitor} from '@angular/cdk/a11y'; @Component({ @@ -14,6 +14,7 @@ import {A11yModule, FocusMonitor} from '@angular/cdk/a11y'; templateUrl: 'focus-origin-demo.html', styleUrl: 'focus-origin-demo.css', imports: [A11yModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FocusOriginDemo { fom = inject(FocusMonitor); diff --git a/src/dev-app/focus-trap/focus-trap-demo.ts b/src/dev-app/focus-trap/focus-trap-demo.ts index e0af4b4b1ed7..1a7c165a8f24 100644 --- a/src/dev-app/focus-trap/focus-trap-demo.ts +++ b/src/dev-app/focus-trap/focus-trap-demo.ts @@ -10,6 +10,7 @@ import {A11yModule, CdkTrapFocus} from '@angular/cdk/a11y'; import {_supportsShadowDom} from '@angular/cdk/platform'; import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -35,6 +36,7 @@ import {MatToolbarModule} from '@angular/material/toolbar'; template: '', host: {'class': 'demo-focus-trap-shadow-root'}, encapsulation: ViewEncapsulation.ShadowDom, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FocusTrapShadowDomDemo {} @@ -43,6 +45,7 @@ export class FocusTrapShadowDomDemo {} templateUrl: 'focus-trap-demo.html', styleUrl: 'focus-trap-demo.css', imports: [A11yModule, MatButtonModule, MatCardModule, MatToolbarModule, FocusTrapShadowDomDemo], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FocusTrapDemo implements AfterViewInit { dialog = inject(MatDialog); @@ -91,6 +94,7 @@ let dialogCount = 0; styleUrl: 'focus-trap-dialog-demo.css', templateUrl: 'focus-trap-dialog-demo.html', imports: [MatDialogTitle, MatDialogContent, MatDialogClose, MatDialogActions], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FocusTrapDialogDemo { dialog = inject(MatDialog); diff --git a/src/dev-app/google-map/google-map-demo.html b/src/dev-app/google-map/google-map-demo.html index b65b0d96ea41..9d4b75dd5a20 100644 --- a/src/dev-app/google-map/google-map-demo.html +++ b/src/dev-app/google-map/google-map-demo.html @@ -72,6 +72,11 @@ @if (directionsResult) { } + @if (isHeatmapDisplayed) { + + }

    {{display?.lat}}

    @@ -201,6 +206,13 @@
    +
    + +
    +
    `, styleUrl: 'theme-demo.css', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ThemeDemoColors { colors = input(); @@ -49,6 +50,7 @@ export class ThemeDemoColors { templateUrl: 'theme-demo.html', styleUrl: 'theme-demo.css', imports: [MatCardModule, MatExpansionModule, MatIconModule, ThemeDemoColors], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ThemeDemo { state = getAppState(); diff --git a/src/dev-app/timepicker/timepicker-demo.ts b/src/dev-app/timepicker/timepicker-demo.ts index e55b02f1b0c9..79fc241fb75d 100644 --- a/src/dev-app/timepicker/timepicker-demo.ts +++ b/src/dev-app/timepicker/timepicker-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, inject, OnDestroy} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, OnDestroy} from '@angular/core'; import {DateAdapter} from '@angular/material/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatTimepickerModule} from '@angular/material/timepicker'; @@ -23,6 +23,7 @@ import {MatDatepickerModule} from '@angular/material/datepicker'; selector: 'timepicker-demo', templateUrl: 'timepicker-demo.html', styleUrl: 'timepicker-demo.css', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatTimepickerModule, MatDatepickerModule, diff --git a/src/dev-app/toolbar/toolbar-demo.ts b/src/dev-app/toolbar/toolbar-demo.ts index a6b76960b8c1..936d10849190 100644 --- a/src/dev-app/toolbar/toolbar-demo.ts +++ b/src/dev-app/toolbar/toolbar-demo.ts @@ -8,7 +8,7 @@ import {ToolbarOverviewExample} from '@angular/components-examples/material/toolbar'; import {ToolbarSimpleExample} from '@angular/components-examples/material/toolbar/toolbar-simple/toolbar-simple-example'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; import {MatToolbarModule} from '@angular/material/toolbar'; @@ -24,5 +24,6 @@ import {MatToolbarModule} from '@angular/material/toolbar'; ToolbarOverviewExample, ToolbarSimpleExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ToolbarDemo {} diff --git a/src/dev-app/tooltip/tooltip-demo.ts b/src/dev-app/tooltip/tooltip-demo.ts index 736c22d0ab46..801cf65f25c0 100644 --- a/src/dev-app/tooltip/tooltip-demo.ts +++ b/src/dev-app/tooltip/tooltip-demo.ts @@ -17,7 +17,7 @@ import { TooltipPositionAtOriginExample, TooltipPositionExample, } from '@angular/components-examples/material/tooltip'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ selector: 'tooltip-demo', @@ -34,5 +34,6 @@ import {Component} from '@angular/core'; TooltipPositionExample, TooltipPositionAtOriginExample, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TooltipDemo {} diff --git a/src/dev-app/tree/tree-demo.ts b/src/dev-app/tree/tree-demo.ts index f886b0435b1f..018860913d03 100644 --- a/src/dev-app/tree/tree-demo.ts +++ b/src/dev-app/tree/tree-demo.ts @@ -25,7 +25,7 @@ import { TreeNestedChildAccessorOverviewExample, TreeFlatChildAccessorOverviewExample, } from '@angular/components-examples/material/tree'; -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -67,5 +67,6 @@ import {MatTreeModule} from '@angular/material/tree'; MatTreeModule, MatProgressBarModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TreeDemo {} diff --git a/src/dev-app/typography/typography-demo.ts b/src/dev-app/typography/typography-demo.ts index 721c4f47c765..d6f4ace5b4b4 100644 --- a/src/dev-app/typography/typography-demo.ts +++ b/src/dev-app/typography/typography-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; @@ -15,5 +15,6 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; templateUrl: 'typography-demo.html', styleUrl: 'typography-demo.css', imports: [MatCheckboxModule, FormsModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class TypographyDemo {} diff --git a/src/dev-app/virtual-scroll/virtual-scroll-demo.ts b/src/dev-app/virtual-scroll/virtual-scroll-demo.ts index f4243af39038..27878ad60c35 100644 --- a/src/dev-app/virtual-scroll/virtual-scroll-demo.ts +++ b/src/dev-app/virtual-scroll/virtual-scroll-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, OnDestroy, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, OnDestroy, ViewEncapsulation} from '@angular/core'; import {AsyncPipe} from '@angular/common'; import {CdkVirtualScrollViewport, ScrollingModule} from '@angular/cdk/scrolling'; import {ScrollingModule as ExperimentalScrollingModule} from '@angular/cdk-experimental/scrolling'; @@ -31,6 +31,7 @@ type State = { templateUrl: 'virtual-scroll-demo.html', styleUrl: 'virtual-scroll-demo.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ AsyncPipe, ExperimentalScrollingModule, diff --git a/src/dev-app/youtube-player/youtube-player-demo.ts b/src/dev-app/youtube-player/youtube-player-demo.ts index 0b2f717afde3..df7eef7198be 100644 --- a/src/dev-app/youtube-player/youtube-player-demo.ts +++ b/src/dev-app/youtube-player/youtube-player-demo.ts @@ -10,6 +10,7 @@ import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -82,6 +83,7 @@ const VIDEOS: Video[] = [ templateUrl: 'youtube-player-demo.html', styleUrl: 'youtube-player-demo.css', imports: [FormsModule, MatRadioModule, MatCheckboxModule, MatButton, YouTubePlayer], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class YouTubePlayerDemo implements AfterViewInit, OnDestroy { @ViewChild('demoYouTubePlayer') demoYouTubePlayer!: ElementRef; diff --git a/src/e2e-app/components/block-scroll-strategy/block-scroll-strategy-e2e.ts b/src/e2e-app/components/block-scroll-strategy/block-scroll-strategy-e2e.ts index d3f554cb1fcd..8f96070568e3 100644 --- a/src/e2e-app/components/block-scroll-strategy/block-scroll-strategy-e2e.ts +++ b/src/e2e-app/components/block-scroll-strategy/block-scroll-strategy-e2e.ts @@ -1,4 +1,4 @@ -import {Component, inject, Injector, ChangeDetectionStrategy} from '@angular/core'; +import {Component, inject, Injector} from '@angular/core'; import {createBlockScrollStrategy, OverlayContainer} from '@angular/cdk/overlay'; import {ScrollingModule} from '@angular/cdk/scrolling'; @@ -7,7 +7,6 @@ import {ScrollingModule} from '@angular/cdk/scrolling'; templateUrl: 'block-scroll-strategy-e2e.html', styleUrl: 'block-scroll-strategy-e2e.css', imports: [ScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) export class BlockScrollStrategyE2E { scrollStrategy = createBlockScrollStrategy(inject(Injector)); diff --git a/src/e2e-app/components/component-harness-e2e.ts b/src/e2e-app/components/component-harness-e2e.ts index 1c562293f8b2..4e18072c37a2 100644 --- a/src/e2e-app/components/component-harness-e2e.ts +++ b/src/e2e-app/components/component-harness-e2e.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy, signal} from '@angular/core'; +import {Component} from '@angular/core'; import {TestMainComponent} from '@angular/cdk/testing/tests'; @Component({ @@ -6,22 +6,21 @@ import {TestMainComponent} from '@angular/cdk/testing/tests'; template: ` - @if (isShown()) { + @if (isShown) { } `, imports: [TestMainComponent], - changeDetection: ChangeDetectionStrategy.Eager, }) export class ComponentHarnessE2e { - protected isShown = signal(true); + protected isShown = true; /** * Resets the test component state without the need to refresh the page. * Used by Webdriver integration tests. */ protected reset(): void { - this.isShown.set(false); - setTimeout(() => this.isShown.set(true), 100); + this.isShown = false; + setTimeout(() => (this.isShown = true), 100); } } diff --git a/src/e2e-app/components/e2e-app/e2e-app.html b/src/e2e-app/components/e2e-app/e2e-app.html index 51ff8d0f0b0f..7bfa37644fa5 100644 --- a/src/e2e-app/components/e2e-app/e2e-app.html +++ b/src/e2e-app/components/e2e-app/e2e-app.html @@ -1,8 +1,8 @@ - +
    - @if (showLinks()) { - + @if (showLinks) { + @for (link of navLinks; track link) { {{link.title}} } diff --git a/src/e2e-app/components/e2e-app/e2e-app.ts b/src/e2e-app/components/e2e-app/e2e-app.ts index 431545f1fa2b..7900365b8479 100644 --- a/src/e2e-app/components/e2e-app/e2e-app.ts +++ b/src/e2e-app/components/e2e-app/e2e-app.ts @@ -1,4 +1,4 @@ -import {Component, ViewEncapsulation, ChangeDetectionStrategy, signal} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; import {MatListModule} from '@angular/material/list'; import {RouterLink, RouterOutlet} from '@angular/router'; @@ -7,10 +7,9 @@ import {RouterLink, RouterOutlet} from '@angular/router'; templateUrl: 'e2e-app.html', encapsulation: ViewEncapsulation.None, imports: [MatListModule, RouterLink, RouterOutlet], - changeDetection: ChangeDetectionStrategy.Eager, }) export class E2eApp { - readonly showLinks = signal(false); + showLinks = false; navLinks = [ {path: 'block-scroll-strategy', title: 'Block Scroll Strategy'}, diff --git a/src/e2e-app/components/home.ts b/src/e2e-app/components/home.ts index 2664d9171f74..28abd4f8adc5 100644 --- a/src/e2e-app/components/home.ts +++ b/src/e2e-app/components/home.ts @@ -1,8 +1,7 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'home', template: `

    Welcome to the e2e tests app

    `, - changeDetection: ChangeDetectionStrategy.Eager, }) export class Home {} diff --git a/src/e2e-app/components/slider-e2e.ts b/src/e2e-app/components/slider-e2e.ts index cc0d5a02f349..fa240b32a389 100644 --- a/src/e2e-app/components/slider-e2e.ts +++ b/src/e2e-app/components/slider-e2e.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {MatSliderModule} from '@angular/material/slider'; @Component({ @@ -27,6 +27,5 @@ import {MatSliderModule} from '@angular/material/slider'; `, styles: '.mat-mdc-slider { width: 148px; }', imports: [MatSliderModule], - changeDetection: ChangeDetectionStrategy.Eager, }) export class SliderE2e {} diff --git a/src/e2e-app/components/virtual-scroll/virtual-scroll-e2e.ts b/src/e2e-app/components/virtual-scroll/virtual-scroll-e2e.ts index 9122c26f5717..1563591be3af 100644 --- a/src/e2e-app/components/virtual-scroll/virtual-scroll-e2e.ts +++ b/src/e2e-app/components/virtual-scroll/virtual-scroll-e2e.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ScrollingModule} from '@angular/cdk/scrolling'; import {ScrollingModule as ExperimentalScrollingModule} from '@angular/cdk-experimental/scrolling'; @@ -9,7 +9,6 @@ const itemSizeSample = [100, 25, 50, 50, 100, 200, 75, 100, 50, 250]; templateUrl: 'virtual-scroll-e2e.html', styleUrl: 'virtual-scroll-e2e.css', imports: [ScrollingModule, ExperimentalScrollingModule], - changeDetection: ChangeDetectionStrategy.Eager, }) export class VirtualScrollE2E { uniformItems = Array(1000).fill(50); diff --git a/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.spec.ts b/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.spec.ts index f4d7b0a26aea..ee41884ef6b4 100644 --- a/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.spec.ts +++ b/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import {MapMarker} from '../map-marker/map-marker'; @@ -51,7 +51,6 @@ describe('DeprecatedMapMarkerClusterer', () => { (window as any).MarkerClusterer = undefined; }); - // We can't test this one easily without `fakeAsync`. it('throws an error if the clustering library has not been loaded', fakeAsync(() => { (window as any).MarkerClusterer = undefined; markerClustererConstructorSpy = createDeprecatedMarkerClustererConstructorSpy( @@ -65,10 +64,9 @@ describe('DeprecatedMapMarkerClusterer', () => { }).toThrowError(/MarkerClusterer class not found, cannot construct a marker cluster/); })); - it('initializes a Google Map Marker Clusterer', async () => { + it('initializes a Google Map Marker Clusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith(mapSpy, [], { ariaLabelFn: undefined, @@ -90,9 +88,9 @@ describe('DeprecatedMapMarkerClusterer', () => { zIndex: undefined, zoomOnClick: undefined, }); - }); + })); - it('sets marker clusterer inputs', async () => { + it('sets marker clusterer inputs', fakeAsync(() => { fixture.componentInstance.ariaLabelFn = (testString: string) => testString; fixture.componentInstance.averageCenter = true; fixture.componentInstance.batchSize = 1; @@ -111,8 +109,7 @@ describe('DeprecatedMapMarkerClusterer', () => { fixture.componentInstance.zoomOnClick = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith(mapSpy, [], { ariaLabelFn: jasmine.any(Function), @@ -134,12 +131,11 @@ describe('DeprecatedMapMarkerClusterer', () => { zIndex: 6, zoomOnClick: true, }); - }); + })); - it('sets marker clusterer options', async () => { + it('sets marker clusterer options', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); const options: MarkerClustererOptions = { enableRetinaIcons: true, gridSize: 1337, @@ -149,15 +145,12 @@ describe('DeprecatedMapMarkerClusterer', () => { fixture.componentInstance.options = options; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); expect(markerClustererSpy.setOptions).toHaveBeenCalledWith(jasmine.objectContaining(options)); - }); + })); - it('gives precedence to specific inputs over options', async () => { + it('gives precedence to specific inputs over options', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); const options: MarkerClustererOptions = { enableRetinaIcons: true, gridSize: 1337, @@ -177,29 +170,25 @@ describe('DeprecatedMapMarkerClusterer', () => { fixture.componentInstance.options = options; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); expect(markerClustererSpy.setOptions).toHaveBeenCalledWith( jasmine.objectContaining(expectedOptions), ); - }); + })); - it('sets Google Maps Markers in the MarkerClusterer', async () => { + it('sets Google Maps Markers in the MarkerClusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([ anyMarkerMatcher, anyMarkerMatcher, ]); - }); + })); - it('updates Google Maps Markers in the Marker Clusterer', async () => { + it('updates Google Maps Markers in the Marker Clusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([ anyMarkerMatcher, @@ -209,8 +198,7 @@ describe('DeprecatedMapMarkerClusterer', () => { fixture.componentInstance.state = 'state2'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true); expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true); @@ -219,8 +207,7 @@ describe('DeprecatedMapMarkerClusterer', () => { fixture.componentInstance.state = 'state0'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([], true); expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith( @@ -228,12 +215,11 @@ describe('DeprecatedMapMarkerClusterer', () => { true, ); expect(markerClustererSpy.repaint).toHaveBeenCalledTimes(2); - }); + })); - it('exposes marker clusterer methods', async () => { + it('exposes marker clusterer methods', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); const markerClustererComponent = fixture.componentInstance.markerClusterer; markerClustererComponent.fitMapToMarkers(5); @@ -300,12 +286,11 @@ describe('DeprecatedMapMarkerClusterer', () => { markerClustererSpy.getZoomOnClick.and.returnValue(true); expect(markerClustererComponent.getZoomOnClick()).toBe(true); - }); + })); - it('initializes marker clusterer event handlers', async () => { + it('initializes marker clusterer event handlers', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addListener).toHaveBeenCalledWith( 'clusteringbegin', @@ -316,13 +301,9 @@ describe('DeprecatedMapMarkerClusterer', () => { jasmine.any(Function), ); expect(markerClustererSpy.addListener).toHaveBeenCalledWith('click', jasmine.any(Function)); - }); + })); }); -function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - @Component({ selector: 'test-app', template: ` @@ -362,7 +343,6 @@ function wait(milliseconds: number) { `, imports: [GoogleMap, MapMarker, DeprecatedMapMarkerClusterer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(DeprecatedMapMarkerClusterer) markerClusterer!: DeprecatedMapMarkerClusterer; diff --git a/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.ts b/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.ts index 6bde5028c0fd..60d245c972cd 100644 --- a/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.ts +++ b/src/google-maps/deprecated-map-marker-clusterer/deprecated-map-marker-clusterer.ts @@ -8,6 +8,7 @@ import { AfterContentInit, + ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, @@ -58,6 +59,7 @@ declare const MarkerClusterer: typeof MarkerClustererInstance; @Component({ selector: 'deprecated-map-marker-clusterer', exportAs: 'mapMarkerClusterer', + changeDetection: ChangeDetectionStrategy.OnPush, template: '', encapsulation: ViewEncapsulation.None, }) @@ -214,6 +216,9 @@ export class DeprecatedMapMarkerClusterer @Output() readonly markerClustererInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._canInitialize) { this._ngZone.runOutsideAngular(() => { @@ -256,7 +261,7 @@ export class DeprecatedMapMarkerClusterer } } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const { markerClusterer: clusterer, ariaLabelFn, diff --git a/src/google-maps/google-map/google-map.spec.ts b/src/google-maps/google-map/google-map.spec.ts index 9392b676e26b..9820c566e2d8 100644 --- a/src/google-maps/google-map/google-map.spec.ts +++ b/src/google-maps/google-map/google-map.spec.ts @@ -1,4 +1,4 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -445,7 +445,6 @@ describe('GoogleMap', () => { (mapInitialized)="mapInitializedSpy($event)" /> `, imports: [GoogleMap], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(GoogleMap) map!: GoogleMap; diff --git a/src/google-maps/google-map/google-map.ts b/src/google-maps/google-map/google-map.ts index 55a197e40c67..93977db85d42 100644 --- a/src/google-maps/google-map/google-map.ts +++ b/src/google-maps/google-map/google-map.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ElementRef, Input, @@ -52,6 +53,7 @@ export const DEFAULT_WIDTH = '500px'; @Component({ selector: 'google-map', exportAs: 'googleMap', + changeDetection: ChangeDetectionStrategy.OnPush, template: '
    ', encapsulation: ViewEncapsulation.None, }) @@ -245,6 +247,8 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { @Output() readonly zoomChanged: Observable = this._eventManager.getLazyEmitter('zoom_changed'); + constructor(...args: unknown[]); + constructor() { const platformId = inject(PLATFORM_ID); this._isBrowser = isPlatformBrowser(platformId); @@ -270,7 +274,7 @@ export class GoogleMap implements OnChanges, OnInit, OnDestroy { } } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (changes['height'] || changes['width']) { this._setSize(); } diff --git a/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts b/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts index 6db115efcdb3..35227ea991fd 100644 --- a/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts +++ b/src/google-maps/map-advanced-marker/map-advanced-marker.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import { @@ -22,32 +22,30 @@ describe('MapAdvancedMarker', () => { (window.google as any) = undefined; }); - it('initializes a Google Map advanced marker', () => { + it('initializes a Google Map advanced marker', fakeAsync(() => { const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS); const advancedMarkerConstructorSpy = createAdvancedMarkerConstructorSpy(advancedMarkerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); - + flush(); expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith({ ...DEFAULT_MARKER_OPTIONS, title: undefined, content: undefined, gmpDraggable: undefined, - gmpClickable: undefined, zIndex: undefined, map: mapSpy, }); - }); + })); - it('sets advanced marker inputs', () => { + it('sets advanced marker inputs', fakeAsync(() => { const options: google.maps.marker.AdvancedMarkerElementOptions = { position: {lat: 3, lng: 5}, title: 'marker title', map: mapSpy, content: undefined, gmpDraggable: true, - gmpClickable: true, zIndex: 1, }; const advancedMarkerSpy = createAdvancedMarkerSpy(options); @@ -58,21 +56,20 @@ describe('MapAdvancedMarker', () => { fixture.componentInstance.title = options.title!; fixture.componentInstance.content = options.content!; fixture.componentInstance.gmpDraggable = options.gmpDraggable!; - fixture.componentInstance.gmpClickable = options.gmpClickable!; fixture.componentInstance.zIndex = options.zIndex!; fixture.detectChanges(); + flush(); expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('sets marker options, ignoring map', () => { + it('sets marker options, ignoring map', fakeAsync(() => { const options: google.maps.marker.AdvancedMarkerElementOptions = { position: {lat: 3, lng: 5}, title: 'marker title', content: undefined, gmpDraggable: true, - gmpClickable: true, zIndex: 1, }; const advancedMarkerSpy = createAdvancedMarkerSpy(options); @@ -81,17 +78,17 @@ describe('MapAdvancedMarker', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = options; fixture.detectChanges(); + flush(); expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith({...options, map: mapSpy}); - }); + })); - it('gives precedence to specific inputs over options', () => { + it('gives precedence to specific inputs over options', fakeAsync(() => { const options: google.maps.marker.AdvancedMarkerElementOptions = { position: {lat: 3, lng: 5}, title: 'marker title', content: undefined, gmpDraggable: true, - gmpClickable: true, zIndex: 1, }; @@ -100,7 +97,6 @@ describe('MapAdvancedMarker', () => { title: 'marker title 2', content: undefined, gmpDraggable: false, - gmpClickable: false, zIndex: 999, map: mapSpy, }; @@ -112,16 +108,16 @@ describe('MapAdvancedMarker', () => { fixture.componentInstance.title = expectedOptions.title!; fixture.componentInstance.content = expectedOptions.content!; fixture.componentInstance.gmpDraggable = expectedOptions.gmpDraggable!; - fixture.componentInstance.gmpClickable = expectedOptions.gmpClickable!; fixture.componentInstance.zIndex = expectedOptions.zIndex!; fixture.componentInstance.options = options!; fixture.detectChanges(); + flush(); expect(advancedMarkerConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('initializes marker event handlers', () => { + it('initializes marker event handlers', fakeAsync(() => { const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS); createAdvancedMarkerConstructorSpy(advancedMarkerSpy); @@ -129,6 +125,7 @@ describe('MapAdvancedMarker', () => { const nativeSpy = advancedMarkerSpy.addEventListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(customSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(nativeSpy).toHaveBeenCalledWith('dblclick', jasmine.any(Function)); @@ -139,16 +136,16 @@ describe('MapAdvancedMarker', () => { expect(customSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function)); expect(customSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function)); expect(customSpy).not.toHaveBeenCalledWith('dragstart', jasmine.any(Function)); - expect(customSpy).not.toHaveBeenCalledWith('gmp-click', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const advancedMarkerSpy = createAdvancedMarkerSpy(DEFAULT_MARKER_OPTIONS); createAdvancedMarkerConstructorSpy(advancedMarkerSpy); const addSpy = advancedMarkerSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('drag', jasmine.any(Function)); @@ -158,7 +155,7 @@ describe('MapAdvancedMarker', () => { expect(addSpy).toHaveBeenCalledWith('drag', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -170,7 +167,6 @@ describe('MapAdvancedMarker', () => { [position]="position" [content]="content" [gmpDraggable]="gmpDraggable" - [gmpClickable]="gmpClickable" [zIndex]="zIndex" (mapClick)="handleClick()" (mapDblclick)="handleDblclick()" @@ -182,7 +178,6 @@ describe('MapAdvancedMarker', () => { `, imports: [GoogleMap, MapAdvancedMarker], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapAdvancedMarker) advancedMarker!: MapAdvancedMarker; @@ -190,7 +185,6 @@ class TestApp { position!: google.maps.LatLng | google.maps.LatLngLiteral; content!: Node | google.maps.marker.PinElement; gmpDraggable!: boolean; - gmpClickable!: boolean; zIndex!: number; options!: google.maps.marker.AdvancedMarkerElementOptions; diff --git a/src/google-maps/map-advanced-marker/map-advanced-marker.ts b/src/google-maps/map-advanced-marker/map-advanced-marker.ts index 825ba1320169..0e0deffa6da5 100644 --- a/src/google-maps/map-advanced-marker/map-advanced-marker.ts +++ b/src/google-maps/map-advanced-marker/map-advanced-marker.ts @@ -105,18 +105,6 @@ export class MapAdvancedMarker } private _draggable: boolean | undefined; - /** - * If `true`, the `AdvancedMarkerElement` will be clickable and trigger the - * gmp-click event, and will be interactive for accessibility purposes (e.g. - * allowing keyboard navigation via arrow keys). - * @see https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions.gmpClickable - */ - @Input() - set gmpClickable(clickable: boolean) { - this._clickable = clickable; - } - private _clickable: boolean | undefined; - /** * Options for constructing an AdvancedMarkerElement. * https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElementOptions @@ -195,14 +183,6 @@ export class MapAdvancedMarker @Output() readonly mapDragstart: Observable = this._eventManager.getLazyEmitter('dragstart'); - /** - * This event is fired when the user starts dragging the AdvancedMarkerElement. - * https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#AdvancedMarkerElement.gmp-click - */ - @Output() - readonly gmpClick: Observable = - this._eventManager.getLazyEmitter('gmp-click'); - /** Event emitted when the marker is initialized. */ @Output() readonly markerInitialized: EventEmitter = new EventEmitter(); @@ -214,6 +194,9 @@ export class MapAdvancedMarker */ advancedMarker!: google.maps.marker.AdvancedMarkerElement; + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (!this._googleMap._isBrowser) { return; @@ -247,8 +230,8 @@ export class MapAdvancedMarker }); } - ngOnChanges(changes: SimpleChanges) { - const {advancedMarker, _content, _position, _title, _draggable, _clickable, _zIndex} = this; + ngOnChanges(changes: SimpleChanges) { + const {advancedMarker, _content, _position, _title, _draggable, _zIndex} = this; if (advancedMarker) { if (changes['title']) { advancedMarker.title = _title; @@ -258,10 +241,6 @@ export class MapAdvancedMarker advancedMarker.gmpDraggable = _draggable; } - if (changes['gmpClickable']) { - advancedMarker.gmpClickable = _clickable; - } - if (changes['content']) { advancedMarker.content = _content; } @@ -307,7 +286,6 @@ export class MapAdvancedMarker content: this._content || options.content, zIndex: this._zIndex ?? options.zIndex, gmpDraggable: this._draggable ?? options.gmpDraggable, - gmpClickable: this._clickable ?? options.gmpClickable, map: this._googleMap.googleMap, }; } diff --git a/src/google-maps/map-base-layer.ts b/src/google-maps/map-base-layer.ts index 26863dfdc415..4756efec1bc7 100644 --- a/src/google-maps/map-base-layer.ts +++ b/src/google-maps/map-base-layer.ts @@ -18,6 +18,9 @@ export class MapBaseLayer implements OnInit, OnDestroy { protected readonly _map = inject(GoogleMap); protected readonly _ngZone = inject(NgZone); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._ngZone.runOutsideAngular(() => { diff --git a/src/google-maps/map-bicycling-layer/map-bicycling-layer.spec.ts b/src/google-maps/map-bicycling-layer/map-bicycling-layer.spec.ts index 3727e7bbab14..1832fdf8709d 100644 --- a/src/google-maps/map-bicycling-layer/map-bicycling-layer.spec.ts +++ b/src/google-maps/map-bicycling-layer/map-bicycling-layer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import { @@ -23,15 +23,16 @@ describe('MapBicyclingLayer', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Bicycling Layer', () => { + it('initializes a Google Map Bicycling Layer', fakeAsync(() => { const bicyclingLayerSpy = createBicyclingLayerSpy(); const bicyclingLayerConstructorSpy = createBicyclingLayerConstructorSpy(bicyclingLayerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(bicyclingLayerConstructorSpy).toHaveBeenCalled(); expect(bicyclingLayerSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); }); @Component({ @@ -42,6 +43,5 @@ describe('MapBicyclingLayer', () => { `, imports: [GoogleMap, MapBicyclingLayer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp {} diff --git a/src/google-maps/map-circle/map-circle.spec.ts b/src/google-maps/map-circle/map-circle.spec.ts index 607e8b6228e1..5e4b86d253ea 100644 --- a/src/google-maps/map-circle/map-circle.spec.ts +++ b/src/google-maps/map-circle/map-circle.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -38,18 +38,19 @@ describe('MapCircle', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Circle', () => { + it('initializes a Google Map Circle', fakeAsync(() => { const circleSpy = createCircleSpy({}); const circleConstructorSpy = createCircleConstructorSpy(circleSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(circleConstructorSpy).toHaveBeenCalledWith({center: undefined, radius: undefined}); expect(circleSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets center and radius from input', () => { + it('sets center and radius from input', fakeAsync(() => { const center: google.maps.LatLngLiteral = {lat: 3, lng: 5}; const radius = 15; const options: google.maps.CircleOptions = {center, radius}; @@ -60,11 +61,12 @@ describe('MapCircle', () => { fixture.componentInstance.center = center; fixture.componentInstance.radius = radius; fixture.detectChanges(); + flush(); expect(circleConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('gives precedence to other inputs over options', () => { + it('gives precedence to other inputs over options', fakeAsync(() => { const center: google.maps.LatLngLiteral = {lat: 3, lng: 5}; const radius = 15; const expectedOptions: google.maps.CircleOptions = {...circleOptions, center, radius}; @@ -76,11 +78,12 @@ describe('MapCircle', () => { fixture.componentInstance.center = center; fixture.componentInstance.radius = radius; fixture.detectChanges(); + flush(); expect(circleConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the Circle', () => { + it('exposes methods that provide information about the Circle', fakeAsync(() => { const circleSpy = createCircleSpy(circleOptions); createCircleConstructorSpy(circleSpy); @@ -89,6 +92,7 @@ describe('MapCircle', () => { .query(By.directive(MapCircle))! .injector.get(MapCircle); fixture.detectChanges(); + flush(); circleComponent.getCenter(); expect(circleSpy.getCenter).toHaveBeenCalled(); @@ -104,15 +108,16 @@ describe('MapCircle', () => { circleSpy.getVisible.and.returnValue(true); expect(circleComponent.getVisible()).toBe(true); - }); + })); - it('initializes Circle event handlers', () => { + it('initializes Circle event handlers', fakeAsync(() => { const circleSpy = createCircleSpy(circleOptions); createCircleConstructorSpy(circleSpy); const addSpy = circleSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('center_changed', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); @@ -127,15 +132,16 @@ describe('MapCircle', () => { expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('radius_changed', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const circleSpy = createCircleSpy(circleOptions); createCircleConstructorSpy(circleSpy); const addSpy = circleSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function)); @@ -145,7 +151,7 @@ describe('MapCircle', () => { expect(addSpy).toHaveBeenCalledWith('dragend', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -161,7 +167,6 @@ describe('MapCircle', () => { (circleRightclick)="handleRightclick()" /> `, imports: [GoogleMap, MapCircle], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapCircle) circle!: MapCircle; diff --git a/src/google-maps/map-circle/map-circle.ts b/src/google-maps/map-circle/map-circle.ts index 39d469f78bc8..e5c029ace825 100644 --- a/src/google-maps/map-circle/map-circle.ts +++ b/src/google-maps/map-circle/map-circle.ts @@ -158,6 +158,9 @@ export class MapCircle implements OnInit, OnDestroy { @Output() readonly circleInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (!this._map._isBrowser) { return; diff --git a/src/google-maps/map-directions-renderer/README.md b/src/google-maps/map-directions-renderer/README.md index c5ff21edc950..6438120f80d5 100644 --- a/src/google-maps/map-directions-renderer/README.md +++ b/src/google-maps/map-directions-renderer/README.md @@ -21,26 +21,28 @@ Using the `MapDirectionsService` requires the Directions API to be enabled in Go ```typescript // google-maps-demo.component.ts -import {Component, inject} from '@angular/core'; +import {Component} from '@angular/core'; import {GoogleMap, MapDirectionsRenderer, MapDirectionsService} from '@angular/google-maps'; -import {map} from 'rxjs/operators'; @Component({ selector: 'google-map-demo', templateUrl: 'google-map-demo.html', - imports: [GoogleMap, MapDirectionsRenderer], + imports: [GoogleMap, MapDirectionsRenderer], }) export class GoogleMapDemo { center: google.maps.LatLngLiteral = {lat: 24, lng: 12}; zoom = 4; - private mapDirectionsService = inject(MapDirectionsService); + readonly directionsResults$: Observable; - readonly directionsResults$ = this.mapDirectionsService.route({ - destination: {lat: 12, lng: 4}, - origin: {lat: 14, lng: 8}, - travelMode: google.maps.TravelMode.DRIVING, - }).pipe(map(response => response.result)); + constructor(mapDirectionsService: MapDirectionsService) { + const request: google.maps.DirectionsRequest = { + destination: {lat: 12, lng: 4}, + origin: {lat: 14, lng: 8}, + travelMode: google.maps.TravelMode.DRIVING + }; + this.directionsResults$ = mapDirectionsService.route(request).pipe(map(response => response.result)); + } } ``` diff --git a/src/google-maps/map-directions-renderer/map-directions-renderer.spec.ts b/src/google-maps/map-directions-renderer/map-directions-renderer.spec.ts index b7dd2ca745ee..36bce6e325e6 100644 --- a/src/google-maps/map-directions-renderer/map-directions-renderer.spec.ts +++ b/src/google-maps/map-directions-renderer/map-directions-renderer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MapDirectionsRenderer} from './map-directions-renderer'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -28,7 +28,7 @@ describe('MapDirectionsRenderer', () => { (window.google as any) = undefined; }); - it('initializes a Google Maps DirectionsRenderer', () => { + it('initializes a Google Maps DirectionsRenderer', fakeAsync(() => { const directionsRendererSpy = createDirectionsRendererSpy({directions: DEFAULT_DIRECTIONS}); const directionsRendererConstructorSpy = createDirectionsRendererConstructorSpy(directionsRendererSpy); @@ -36,15 +36,16 @@ describe('MapDirectionsRenderer', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = {directions: DEFAULT_DIRECTIONS}; fixture.detectChanges(); + flush(); expect(directionsRendererConstructorSpy).toHaveBeenCalledWith({ directions: DEFAULT_DIRECTIONS, map: jasmine.any(Object), }); expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets directions from directions input', () => { + it('sets directions from directions input', fakeAsync(() => { const directionsRendererSpy = createDirectionsRendererSpy({directions: DEFAULT_DIRECTIONS}); const directionsRendererConstructorSpy = createDirectionsRendererConstructorSpy(directionsRendererSpy); @@ -52,15 +53,16 @@ describe('MapDirectionsRenderer', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.directions = DEFAULT_DIRECTIONS; fixture.detectChanges(); + flush(); expect(directionsRendererConstructorSpy).toHaveBeenCalledWith({ directions: DEFAULT_DIRECTIONS, map: jasmine.any(Object), }); expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('gives precedence to directions over options', () => { + it('gives precedence to directions over options', fakeAsync(() => { const updatedDirections: google.maps.DirectionsResult = { geocoded_waypoints: [{partial_match: false, place_id: 'test', types: []}], request: { @@ -78,15 +80,16 @@ describe('MapDirectionsRenderer', () => { fixture.componentInstance.options = {directions: DEFAULT_DIRECTIONS}; fixture.componentInstance.directions = updatedDirections; fixture.detectChanges(); + flush(); expect(directionsRendererConstructorSpy).toHaveBeenCalledWith({ directions: updatedDirections, map: jasmine.any(Object), }); expect(directionsRendererSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('exposes methods that provide information from the DirectionsRenderer', () => { + it('exposes methods that provide information from the DirectionsRenderer', fakeAsync(() => { const directionsRendererSpy = createDirectionsRendererSpy({}); createDirectionsRendererConstructorSpy(directionsRendererSpy); @@ -96,6 +99,7 @@ describe('MapDirectionsRenderer', () => { .query(By.directive(MapDirectionsRenderer))! .injector.get(MapDirectionsRenderer); fixture.detectChanges(); + flush(); directionsRendererSpy.getDirections.and.returnValue(DEFAULT_DIRECTIONS); expect(directionsRendererComponent.getDirections()).toBe(DEFAULT_DIRECTIONS); @@ -105,20 +109,21 @@ describe('MapDirectionsRenderer', () => { directionsRendererSpy.getRouteIndex.and.returnValue(10); expect(directionsRendererComponent.getRouteIndex()).toBe(10); - }); + })); - it('initializes DirectionsRenderer event handlers', () => { + it('initializes DirectionsRenderer event handlers', fakeAsync(() => { const directionsRendererSpy = createDirectionsRendererSpy({}); createDirectionsRendererConstructorSpy(directionsRendererSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(directionsRendererSpy.addListener).toHaveBeenCalledWith( 'directions_changed', jasmine.any(Function), ); - }); + })); }); @Component({ @@ -132,7 +137,6 @@ describe('MapDirectionsRenderer', () => { `, imports: [GoogleMap, MapDirectionsRenderer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapDirectionsRenderer) directionsRenderer!: MapDirectionsRenderer; diff --git a/src/google-maps/map-directions-renderer/map-directions-renderer.ts b/src/google-maps/map-directions-renderer/map-directions-renderer.ts index a67af711c637..960a737d3f8d 100644 --- a/src/google-maps/map-directions-renderer/map-directions-renderer.ts +++ b/src/google-maps/map-directions-renderer/map-directions-renderer.ts @@ -72,6 +72,9 @@ export class MapDirectionsRenderer implements OnInit, OnChanges, OnDestroy { /** The underlying google.maps.DirectionsRenderer object. */ directionsRenderer?: google.maps.DirectionsRenderer; + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._googleMap._isBrowser) { if (google.maps.DirectionsRenderer && this._googleMap.googleMap) { @@ -104,7 +107,7 @@ export class MapDirectionsRenderer implements OnInit, OnChanges, OnDestroy { }); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (this.directionsRenderer) { if (changes['options']) { this.directionsRenderer.setOptions(this._combineOptions()); diff --git a/src/google-maps/map-directions-renderer/map-directions-service.ts b/src/google-maps/map-directions-renderer/map-directions-service.ts index 9c11a3bf535c..b9aec16cb1b0 100644 --- a/src/google-maps/map-directions-renderer/map-directions-service.ts +++ b/src/google-maps/map-directions-renderer/map-directions-service.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import {Observable} from 'rxjs'; export interface MapDirectionsResponse { @@ -20,11 +20,14 @@ export interface MapDirectionsResponse { * * See developers.google.com/maps/documentation/javascript/reference/directions#DirectionsService */ -@Service() +@Injectable({providedIn: 'root'}) export class MapDirectionsService { private readonly _ngZone = inject(NgZone); private _directionsService: google.maps.DirectionsService | undefined; + constructor(...args: unknown[]); + constructor() {} + /** * See * developers.google.com/maps/documentation/javascript/reference/directions @@ -35,10 +38,7 @@ export class MapDirectionsService { this._getService().then(service => { service.route(request, (result, status) => { this._ngZone.run(() => { - observer.next({ - result: result || undefined, - status: status as google.maps.DirectionsStatus, - }); + observer.next({result: result || undefined, status}); observer.complete(); }); }); diff --git a/src/google-maps/map-geocoder/README.md b/src/google-maps/map-geocoder/README.md index 8dc3a3ee628d..f6b8257fc9e4 100644 --- a/src/google-maps/map-geocoder/README.md +++ b/src/google-maps/map-geocoder/README.md @@ -24,7 +24,7 @@ has billing enabled. See [here](https://developers.google.com/maps/documentation ```typescript // google-maps-demo.component.ts -import {Component, inject} from '@angular/core'; +import {Component} from '@angular/core'; import {MapGeocoder} from '@angular/google-maps'; @Component({ @@ -32,10 +32,8 @@ import {MapGeocoder} from '@angular/google-maps'; templateUrl: 'google-map-demo.html', }) export class GoogleMapDemo { - private geocoder = inject(MapGeocoder); - - search() { - this.geocoder.geocode({ + constructor(geocoder: MapGeocoder) { + geocoder.geocode({ address: '1600 Amphitheatre Parkway, Mountain View, CA' }).subscribe(({results}) => { console.log(results); diff --git a/src/google-maps/map-geocoder/map-geocoder.ts b/src/google-maps/map-geocoder/map-geocoder.ts index eaee17f388ba..412977d0287d 100644 --- a/src/google-maps/map-geocoder/map-geocoder.ts +++ b/src/google-maps/map-geocoder/map-geocoder.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, NgZone, inject} from '@angular/core'; +import {Injectable, NgZone, inject} from '@angular/core'; import {Observable} from 'rxjs'; export interface MapGeocoderResponse { @@ -18,11 +18,14 @@ export interface MapGeocoderResponse { * Angular service that wraps the Google Maps Geocoder from the Google Maps JavaScript API. * See developers.google.com/maps/documentation/javascript/reference/geocoder#Geocoder */ -@Service() +@Injectable({providedIn: 'root'}) export class MapGeocoder { private readonly _ngZone = inject(NgZone); private _geocoder: google.maps.Geocoder | undefined; + constructor(...args: unknown[]); + constructor() {} + /** * See developers.google.com/maps/documentation/javascript/reference/geocoder#Geocoder.geocode */ @@ -31,7 +34,7 @@ export class MapGeocoder { this._getGeocoder().then(geocoder => { geocoder.geocode(request, (results, status) => { this._ngZone.run(() => { - observer.next({results: results || [], status: status as google.maps.GeocoderStatus}); + observer.next({results: results || [], status}); observer.complete(); }); }); diff --git a/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts b/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts index c27f814b1e66..4af6c550cbc5 100644 --- a/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts +++ b/src/google-maps/map-ground-overlay/map-ground-overlay.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -29,7 +29,7 @@ describe('MapGroundOverlay', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Ground Overlay', () => { + it('initializes a Google Map Ground Overlay', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); const groundOverlayConstructorSpy = createGroundOverlayConstructorSpy(groundOverlaySpy); @@ -40,12 +40,13 @@ describe('MapGroundOverlay', () => { fixture.componentInstance.opacity = opacity; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(groundOverlayConstructorSpy).toHaveBeenCalledWith(url, bounds, groundOverlayOptions); expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('exposes methods that provide information about the Ground Overlay', () => { + it('exposes methods that provide information about the Ground Overlay', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); createGroundOverlayConstructorSpy(groundOverlaySpy); @@ -58,6 +59,7 @@ describe('MapGroundOverlay', () => { fixture.componentInstance.opacity = opacity; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); groundOverlayComponent.getBounds(); expect(groundOverlaySpy.getBounds).toHaveBeenCalled(); @@ -67,9 +69,9 @@ describe('MapGroundOverlay', () => { groundOverlaySpy.getUrl.and.returnValue(url); expect(groundOverlayComponent.getUrl()).toBe(url); - }); + })); - it('initializes Ground Overlay event handlers', () => { + it('initializes Ground Overlay event handlers', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); createGroundOverlayConstructorSpy(groundOverlaySpy); @@ -79,12 +81,13 @@ describe('MapGroundOverlay', () => { fixture.componentInstance.bounds = bounds; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); createGroundOverlayConstructorSpy(groundOverlaySpy); @@ -94,6 +97,7 @@ describe('MapGroundOverlay', () => { fixture.componentInstance.bounds = bounds; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function)); @@ -103,9 +107,9 @@ describe('MapGroundOverlay', () => { expect(addSpy).toHaveBeenCalledWith('dblclick', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); - it('should be able to change the image after init', () => { + it('should be able to change the image after init', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); const groundOverlayConstructorSpy = createGroundOverlayConstructorSpy(groundOverlaySpy); @@ -116,6 +120,7 @@ describe('MapGroundOverlay', () => { fixture.componentInstance.opacity = opacity; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(groundOverlayConstructorSpy).toHaveBeenCalledWith(url, bounds, groundOverlayOptions); expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); @@ -129,24 +134,26 @@ describe('MapGroundOverlay', () => { expect(groundOverlaySpy.setMap).toHaveBeenCalledTimes(2); expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(null); expect(groundOverlaySpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('should recreate the ground overlay when the bounds change', () => { + it('should recreate the ground overlay when the bounds change', fakeAsync(() => { const groundOverlaySpy = createGroundOverlaySpy(url, bounds, groundOverlayOptions); createGroundOverlayConstructorSpy(groundOverlaySpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); const oldOverlay = fixture.componentInstance.groundOverlay.groundOverlay; fixture.componentInstance.bounds = {...bounds}; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); const newOverlay = fixture.componentInstance.groundOverlay.groundOverlay; expect(newOverlay).toBeTruthy(); expect(newOverlay).not.toBe(oldOverlay); - }); + })); }); @Component({ @@ -162,7 +169,6 @@ describe('MapGroundOverlay', () => { `, imports: [GoogleMap, MapGroundOverlay], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapGroundOverlay) groundOverlay!: MapGroundOverlay; diff --git a/src/google-maps/map-ground-overlay/map-ground-overlay.ts b/src/google-maps/map-ground-overlay/map-ground-overlay.ts index e7eba1d06f3e..450cc9006e1a 100644 --- a/src/google-maps/map-ground-overlay/map-ground-overlay.ts +++ b/src/google-maps/map-ground-overlay/map-ground-overlay.ts @@ -93,6 +93,9 @@ export class MapGroundOverlay implements OnInit, OnDestroy { @Output() readonly groundOverlayInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { // The ground overlay setup is slightly different from the other Google Maps objects in that diff --git a/src/google-maps/map-heatmap-layer/README.md b/src/google-maps/map-heatmap-layer/README.md new file mode 100644 index 000000000000..ba64f2017132 --- /dev/null +++ b/src/google-maps/map-heatmap-layer/README.md @@ -0,0 +1,48 @@ +# MapHeatmapLayer + +The `MapHeatmapLayer` directive wraps the [`google.maps.visualization.HeatmapLayer` class](https://developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayer) from the Google Maps Visualization JavaScript API. It displays +a heatmap layer on the map when it is a content child of a `GoogleMap` component. Like `GoogleMap`, +this directive offers an `options` input as well as a convenience input for passing in the `data` +that is shown on the heatmap. + +## Example + +```typescript +// google-map-demo.component.ts +import {Component} from '@angular/core'; +import {GoogleMap, MapHeatmapLayer} from '@angular/google-maps'; + +@Component({ + selector: 'google-map-demo', + templateUrl: 'google-map-demo.html', + imports: [GoogleMap, MapHeatmapLayer], +}) +export class GoogleMapDemo { + center = {lat: 37.774546, lng: -122.433523}; + zoom = 12; + heatmapOptions = {radius: 5}; + heatmapData = [ + {lat: 37.782, lng: -122.447}, + {lat: 37.782, lng: -122.445}, + {lat: 37.782, lng: -122.443}, + {lat: 37.782, lng: -122.441}, + {lat: 37.782, lng: -122.439}, + {lat: 37.782, lng: -122.437}, + {lat: 37.782, lng: -122.435}, + {lat: 37.785, lng: -122.447}, + {lat: 37.785, lng: -122.445}, + {lat: 37.785, lng: -122.443}, + {lat: 37.785, lng: -122.441}, + {lat: 37.785, lng: -122.439}, + {lat: 37.785, lng: -122.437}, + {lat: 37.785, lng: -122.435} + ]; +} +``` + +```html + + + + +``` diff --git a/src/google-maps/map-heatmap-layer/map-heatmap-layer.spec.ts b/src/google-maps/map-heatmap-layer/map-heatmap-layer.spec.ts new file mode 100644 index 000000000000..eb1af7494648 --- /dev/null +++ b/src/google-maps/map-heatmap-layer/map-heatmap-layer.spec.ts @@ -0,0 +1,172 @@ +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; + +import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; + +import { + createHeatmapLayerConstructorSpy, + createHeatmapLayerSpy, + createLatLngConstructorSpy, + createLatLngSpy, + createMapConstructorSpy, + createMapSpy, +} from '../testing/fake-google-map-utils'; +import {HeatmapData, MapHeatmapLayer} from './map-heatmap-layer'; + +describe('MapHeatmapLayer', () => { + let mapSpy: jasmine.SpyObj; + let latLngSpy: jasmine.SpyObj; + + beforeEach(() => { + mapSpy = createMapSpy(DEFAULT_OPTIONS); + latLngSpy = createLatLngSpy(); + createMapConstructorSpy(mapSpy); + createLatLngConstructorSpy(latLngSpy); + }); + + afterEach(() => { + (window.google as any) = undefined; + }); + + it('initializes a Google Map heatmap layer', fakeAsync(() => { + const heatmapSpy = createHeatmapLayerSpy(); + const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy); + + const fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + flush(); + + expect(heatmapConstructorSpy).toHaveBeenCalledWith({ + data: [], + map: mapSpy, + }); + })); + + it('should throw if the `visualization` library has not been loaded', fakeAsync(() => { + createHeatmapLayerConstructorSpy(createHeatmapLayerSpy()); + delete (window.google.maps as any).visualization; + + expect(() => { + const fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + flush(); + }).toThrowError(/Namespace `google.maps.visualization` not found, cannot construct heatmap/); + })); + + it('sets heatmap inputs', fakeAsync(() => { + const options: google.maps.visualization.HeatmapLayerOptions = { + map: mapSpy, + data: [ + new google.maps.LatLng(37.782, -122.447), + new google.maps.LatLng(37.782, -122.445), + new google.maps.LatLng(37.782, -122.443), + ], + }; + const heatmapSpy = createHeatmapLayerSpy(); + const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.data = options.data!; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + flush(); + + expect(heatmapConstructorSpy).toHaveBeenCalledWith(options); + })); + + it('sets heatmap options, ignoring map', fakeAsync(() => { + const options: Partial = { + radius: 5, + dissipating: true, + }; + const data = [ + new google.maps.LatLng(37.782, -122.447), + new google.maps.LatLng(37.782, -122.445), + new google.maps.LatLng(37.782, -122.443), + ]; + const heatmapSpy = createHeatmapLayerSpy(); + const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy); + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.data = data; + fixture.componentInstance.options = options; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + flush(); + + expect(heatmapConstructorSpy).toHaveBeenCalledWith({...options, map: mapSpy, data}); + })); + + it('exposes methods that provide information about the heatmap', fakeAsync(() => { + const heatmapSpy = createHeatmapLayerSpy(); + createHeatmapLayerConstructorSpy(heatmapSpy); + + const fixture = TestBed.createComponent(TestApp); + fixture.detectChanges(); + flush(); + const heatmap = fixture.componentInstance.heatmap; + + heatmapSpy.getData.and.returnValue([] as any); + expect(heatmap.getData()).toEqual([]); + })); + + it('should update the heatmap data when the input changes', fakeAsync(() => { + const heatmapSpy = createHeatmapLayerSpy(); + const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy); + let data = [ + new google.maps.LatLng(1, 2), + new google.maps.LatLng(3, 4), + new google.maps.LatLng(5, 6), + ]; + + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.data = data; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + flush(); + + expect(heatmapConstructorSpy).toHaveBeenCalledWith(jasmine.objectContaining({data})); + data = [ + new google.maps.LatLng(7, 8), + new google.maps.LatLng(9, 10), + new google.maps.LatLng(11, 12), + ]; + fixture.componentInstance.data = data; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + expect(heatmapSpy.setData).toHaveBeenCalledWith(data); + })); + + it('should create a LatLng object if a LatLngLiteral is passed in', fakeAsync(() => { + const latLngConstructor = createLatLngConstructorSpy(latLngSpy); + createHeatmapLayerConstructorSpy(createHeatmapLayerSpy()); + const fixture = TestBed.createComponent(TestApp); + fixture.componentInstance.data = [ + {lat: 1, lng: 2}, + {lat: 3, lng: 4}, + ]; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + flush(); + + expect(latLngConstructor).toHaveBeenCalledWith(1, 2); + expect(latLngConstructor).toHaveBeenCalledWith(3, 4); + expect(latLngConstructor).toHaveBeenCalledTimes(2); + })); +}); + +@Component({ + selector: 'test-app', + template: ` + + + + `, + imports: [GoogleMap, MapHeatmapLayer], +}) +class TestApp { + @ViewChild(MapHeatmapLayer) heatmap!: MapHeatmapLayer; + options!: Partial; + data!: HeatmapData; +} diff --git a/src/google-maps/map-heatmap-layer/map-heatmap-layer.ts b/src/google-maps/map-heatmap-layer/map-heatmap-layer.ts index 9560913c2917..fb69a4a703f8 100644 --- a/src/google-maps/map-heatmap-layer/map-heatmap-layer.ts +++ b/src/google-maps/map-heatmap-layer/map-heatmap-layer.ts @@ -6,59 +6,135 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Input, Directive, Output, EventEmitter} from '@angular/core'; +import { + Input, + OnDestroy, + OnInit, + NgZone, + Directive, + OnChanges, + SimpleChanges, + Output, + EventEmitter, + inject, +} from '@angular/core'; -/** - * Possible data that can be shown on a heatmap layer. - * @deprecated Google Maps no longer supports the heatmap API. - * @breaking-change 23.0.0 - */ -export type HeatmapData = any; +import {GoogleMap} from '../google-map/google-map'; + +/** Possible data that can be shown on a heatmap layer. */ +export type HeatmapData = + | google.maps.MVCArray< + google.maps.LatLng | google.maps.visualization.WeightedLocation | google.maps.LatLngLiteral + > + | (google.maps.LatLng | google.maps.visualization.WeightedLocation | google.maps.LatLngLiteral)[]; /** * Angular directive that renders a Google Maps heatmap via the Google Maps JavaScript API. * * See: https://developers.google.com/maps/documentation/javascript/reference/visualization - * - * @deprecated Google Maps no longer supports the heatmap API so this component is now a no-op that will be deleted. - * @breaking-change 23.0.0 */ @Directive({ selector: 'map-heatmap-layer', exportAs: 'mapHeatmapLayer', }) -export class MapHeatmapLayer { +export class MapHeatmapLayer implements OnInit, OnChanges, OnDestroy { + private readonly _googleMap = inject(GoogleMap); + private _ngZone = inject(NgZone); + /** * Data shown on the heatmap. * See: https://developers.google.com/maps/documentation/javascript/reference/visualization */ @Input() - set data(data: HeatmapData) {} + set data(data: HeatmapData) { + this._data = data; + } + private _data!: HeatmapData; /** * Options used to configure the heatmap. See: * developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayerOptions */ @Input() - set options(options: any) {} + set options(options: Partial) { + this._options = options; + } + private _options!: Partial; /** * The underlying google.maps.visualization.HeatmapLayer object. * * See: https://developers.google.com/maps/documentation/javascript/reference/visualization */ - heatmap?: any; + heatmap?: google.maps.visualization.HeatmapLayer; /** Event emitted when the heatmap is initialized. */ - @Output() readonly heatmapInitialized: EventEmitter = new EventEmitter(); - - constructor() { - console.error( - 'As of May 2026, Google Maps no longer supports the heatmap layer APIs. ' + - 'As a result, the `` component is a no-op that will ' + - 'be removed completely in Angular v23.\nMore information: ' + - 'https://developers.google.com/maps/deprecations?utm_source=devtools&utm_campaign=stable#heatmap-layer-js-deprecation', - ); + @Output() readonly heatmapInitialized: EventEmitter = + new EventEmitter(); + + constructor(...args: unknown[]); + constructor() {} + + ngOnInit() { + if (this._googleMap._isBrowser) { + if ( + !window.google?.maps?.visualization && + !window.google?.maps.importLibrary && + (typeof ngDevMode === 'undefined' || ngDevMode) + ) { + throw Error( + 'Namespace `google.maps.visualization` not found, cannot construct heatmap. ' + + 'Please install the Google Maps JavaScript API with the "visualization" library: ' + + 'https://developers.google.com/maps/documentation/javascript/visualization', + ); + } + + if (google.maps.visualization?.HeatmapLayer && this._googleMap.googleMap) { + this._initialize(this._googleMap.googleMap, google.maps.visualization.HeatmapLayer); + } else { + this._ngZone.runOutsideAngular(() => { + Promise.all([ + this._googleMap._resolveMap(), + google.maps.importLibrary('visualization'), + ]).then(([map, lib]) => { + this._initialize(map, (lib as google.maps.VisualizationLibrary).HeatmapLayer); + }); + }); + } + } + } + + private _initialize( + map: google.maps.Map, + heatmapConstructor: typeof google.maps.visualization.HeatmapLayer, + ) { + // Create the object outside the zone so its events don't trigger change detection. + // We'll bring it back in inside the `MapEventManager` only for the events that the + // user has subscribed to. + this._ngZone.runOutsideAngular(() => { + this.heatmap = new heatmapConstructor(this._combineOptions()); + this._assertInitialized(); + this.heatmap.setMap(map); + this.heatmapInitialized.emit(this.heatmap); + }); + } + + ngOnChanges(changes: SimpleChanges) { + const {_data, heatmap} = this; + + if (heatmap) { + if (changes['options']) { + heatmap.setOptions(this._combineOptions()); + } + + if (changes['data'] && _data !== undefined) { + heatmap.setData(this._normalizeData(_data)); + } + } + } + + ngOnDestroy() { + this.heatmap?.setMap(null); } /** @@ -66,6 +142,52 @@ export class MapHeatmapLayer { * See: developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayer */ getData(): HeatmapData { - return null; + this._assertInitialized(); + return this.heatmap.getData(); + } + + /** Creates a combined options object using the passed-in options and the individual inputs. */ + private _combineOptions(): google.maps.visualization.HeatmapLayerOptions { + const options = this._options || {}; + return { + ...options, + data: this._normalizeData(this._data || options.data || []), + map: this._googleMap.googleMap, + }; } + + /** + * Most Google Maps APIs support both `LatLng` objects and `LatLngLiteral`. The latter is more + * convenient to write out, because the Google Maps API doesn't have to have been loaded in order + * to construct them. The `HeatmapLayer` appears to be an exception that only allows a `LatLng` + * object, or it throws a runtime error. Since it's more convenient and we expect that Angular + * users will load the API asynchronously, we allow them to pass in a `LatLngLiteral` and we + * convert it to a `LatLng` object before passing it off to Google Maps. + */ + private _normalizeData(data: HeatmapData) { + const result: (google.maps.LatLng | google.maps.visualization.WeightedLocation)[] = []; + + data.forEach(item => { + result.push(isLatLngLiteral(item) ? new google.maps.LatLng(item.lat, item.lng) : item); + }); + + return result; + } + + /** Asserts that the heatmap object has been initialized. */ + private _assertInitialized(): asserts this is {heatmap: google.maps.visualization.HeatmapLayer} { + if (typeof ngDevMode === 'undefined' || ngDevMode) { + if (!this.heatmap) { + throw Error( + 'Cannot interact with a Google Map HeatmapLayer before it has been ' + + 'initialized. Please wait for the heatmap to load before trying to interact with it.', + ); + } + } + } +} + +/** Asserts that an object is a `LatLngLiteral`. */ +function isLatLngLiteral(value: any): value is google.maps.LatLngLiteral { + return value && typeof value.lat === 'number' && typeof value.lng === 'number'; } diff --git a/src/google-maps/map-info-window/map-info-window.spec.ts b/src/google-maps/map-info-window/map-info-window.spec.ts index 82bd8c380dd4..b3d2a7d2888e 100644 --- a/src/google-maps/map-info-window/map-info-window.spec.ts +++ b/src/google-maps/map-info-window/map-info-window.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -25,7 +25,7 @@ describe('MapInfoWindow', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Info Window', () => { + it('initializes a Google Map Info Window', fakeAsync(() => { const infoWindowSpy = createInfoWindowSpy({}); const infoWindowConstructorSpy = createInfoWindowConstructorSpy(infoWindowSpy); @@ -36,9 +36,9 @@ describe('MapInfoWindow', () => { position: undefined, content: jasmine.any(Node), }); - }); + })); - it('sets position', () => { + it('sets position', fakeAsync(() => { const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; const infoWindowSpy = createInfoWindowSpy({position}); const infoWindowConstructorSpy = createInfoWindowConstructorSpy(infoWindowSpy); @@ -46,14 +46,15 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.position = position; fixture.detectChanges(); + flush(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ position, content: jasmine.any(Node), }); - }); + })); - it('sets options', () => { + it('sets options', fakeAsync(() => { const options: google.maps.InfoWindowOptions = { position: {lat: 3, lng: 5}, maxWidth: 50, @@ -65,14 +66,15 @@ describe('MapInfoWindow', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = options; fixture.detectChanges(); + flush(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ ...options, content: jasmine.any(Node), }); - }); + })); - it('gives preference to position over options', () => { + it('gives preference to position over options', fakeAsync(() => { const position: google.maps.LatLngLiteral = {lat: 5, lng: 7}; const options: google.maps.InfoWindowOptions = { position: {lat: 3, lng: 5}, @@ -86,15 +88,16 @@ describe('MapInfoWindow', () => { fixture.componentInstance.options = options; fixture.componentInstance.position = position; fixture.detectChanges(); + flush(); expect(infoWindowConstructorSpy).toHaveBeenCalledWith({ ...options, position, content: jasmine.any(Node), }); - }); + })); - it('exposes methods that change the configuration of the info window', () => { + it('exposes methods that change the configuration of the info window', fakeAsync(() => { const fakeMarker = {} as unknown as google.maps.Marker; const fakeMarkerComponent = { marker: fakeMarker, @@ -108,6 +111,7 @@ describe('MapInfoWindow', () => { .query(By.directive(MapInfoWindow))! .injector.get(MapInfoWindow); fixture.detectChanges(); + flush(); infoWindowComponent.close(); expect(infoWindowSpy.close).toHaveBeenCalled(); @@ -120,9 +124,9 @@ describe('MapInfoWindow', () => { shouldFocus: undefined, }), ); - }); + })); - it('should not try to reopen info window multiple times for the same marker', () => { + it('should not try to reopen info window multiple times for the same marker', fakeAsync(() => { const fakeMarker = {} as unknown as google.maps.Marker; const fakeMarkerComponent = { marker: fakeMarker, @@ -136,6 +140,7 @@ describe('MapInfoWindow', () => { .query(By.directive(MapInfoWindow))! .injector.get(MapInfoWindow); fixture.detectChanges(); + flush(); infoWindowComponent.open(fakeMarkerComponent); expect(infoWindowSpy.open).toHaveBeenCalledTimes(1); @@ -146,9 +151,9 @@ describe('MapInfoWindow', () => { infoWindowComponent.close(); infoWindowComponent.open(fakeMarkerComponent); expect(infoWindowSpy.open).toHaveBeenCalledTimes(2); - }); + })); - it('exposes methods that provide information about the info window', () => { + it('exposes methods that provide information about the info window', fakeAsync(() => { const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy); @@ -157,6 +162,7 @@ describe('MapInfoWindow', () => { .query(By.directive(MapInfoWindow))! .injector.get(MapInfoWindow); fixture.detectChanges(); + flush(); infoWindowSpy.getContent.and.returnValue('test content'); expect(infoWindowComponent.getContent()).toBe('test content'); @@ -166,30 +172,32 @@ describe('MapInfoWindow', () => { infoWindowSpy.getZIndex.and.returnValue(5); expect(infoWindowComponent.getZIndex()).toBe(5); - }); + })); - it('initializes info window event handlers', () => { + it('initializes info window event handlers', fakeAsync(() => { const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy); const addSpy = infoWindowSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('closeclick', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('content_changed', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('domready', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('position_changed', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('zindex_changed', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy); const addSpy = infoWindowSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('zindex_changed', jasmine.any(Function)); @@ -199,9 +207,9 @@ describe('MapInfoWindow', () => { expect(addSpy).toHaveBeenCalledWith('zindex_changed', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); - it('should be able to open an info window without passing in an anchor', () => { + it('should be able to open an info window without passing in an anchor', fakeAsync(() => { const infoWindowSpy = createInfoWindowSpy({}); createInfoWindowConstructorSpy(infoWindowSpy); @@ -210,12 +218,13 @@ describe('MapInfoWindow', () => { .query(By.directive(MapInfoWindow))! .injector.get(MapInfoWindow); fixture.detectChanges(); + flush(); infoWindowComponent.open(); expect(infoWindowSpy.open).toHaveBeenCalledTimes(1); - }); + })); - it('should allow for the focus behavior to be changed when opening the info window', () => { + it('should allow for the focus behavior to be changed when opening the info window', fakeAsync(() => { const fakeMarker = {} as unknown as google.maps.Marker; const fakeMarkerComponent = { marker: fakeMarker, @@ -229,6 +238,7 @@ describe('MapInfoWindow', () => { .query(By.directive(MapInfoWindow))! .injector.get(MapInfoWindow); fixture.detectChanges(); + flush(); infoWindowComponent.open(fakeMarkerComponent, false); expect(infoWindowSpy.open).toHaveBeenCalledWith( @@ -236,7 +246,7 @@ describe('MapInfoWindow', () => { shouldFocus: false, }), ); - }); + })); }); @Component({ @@ -249,7 +259,6 @@ describe('MapInfoWindow', () => { `, imports: [GoogleMap, MapInfoWindow], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapInfoWindow) infoWindow!: MapInfoWindow; diff --git a/src/google-maps/map-info-window/map-info-window.ts b/src/google-maps/map-info-window/map-info-window.ts index 407c9f3d3fe0..f1232e52dc8e 100644 --- a/src/google-maps/map-info-window/map-info-window.ts +++ b/src/google-maps/map-info-window/map-info-window.ts @@ -104,6 +104,9 @@ export class MapInfoWindow implements OnInit, OnDestroy { @Output() readonly infoWindowInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._googleMap._isBrowser) { this._combineOptions() diff --git a/src/google-maps/map-kml-layer/map-kml-layer.spec.ts b/src/google-maps/map-kml-layer/map-kml-layer.spec.ts index c67dba1a8924..13e9c13d5805 100644 --- a/src/google-maps/map-kml-layer/map-kml-layer.spec.ts +++ b/src/google-maps/map-kml-layer/map-kml-layer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -31,18 +31,19 @@ describe('MapKmlLayer', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Kml Layer', () => { + it('initializes a Google Map Kml Layer', fakeAsync(() => { const kmlLayerSpy = createKmlLayerSpy({}); const kmlLayerConstructorSpy = createKmlLayerConstructorSpy(kmlLayerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(kmlLayerConstructorSpy).toHaveBeenCalledWith({url: undefined}); expect(kmlLayerSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets url from input', () => { + it('sets url from input', fakeAsync(() => { const options: google.maps.KmlLayerOptions = {url: DEMO_URL}; const kmlLayerSpy = createKmlLayerSpy(options); const kmlLayerConstructorSpy = createKmlLayerConstructorSpy(kmlLayerSpy); @@ -50,11 +51,12 @@ describe('MapKmlLayer', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.url = DEMO_URL; fixture.detectChanges(); + flush(); expect(kmlLayerConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('gives precedence to url input over options', () => { + it('gives precedence to url input over options', fakeAsync(() => { const expectedUrl = 'www.realurl.kml'; const expectedOptions: google.maps.KmlLayerOptions = {...DEFAULT_KML_OPTIONS, url: expectedUrl}; const kmlLayerSpy = createKmlLayerSpy(expectedOptions); @@ -64,11 +66,12 @@ describe('MapKmlLayer', () => { fixture.componentInstance.options = DEFAULT_KML_OPTIONS; fixture.componentInstance.url = expectedUrl; fixture.detectChanges(); + flush(); expect(kmlLayerConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the KmlLayer', () => { + it('exposes methods that provide information about the KmlLayer', fakeAsync(() => { const kmlLayerSpy = createKmlLayerSpy(DEFAULT_KML_OPTIONS); createKmlLayerConstructorSpy(kmlLayerSpy); @@ -77,6 +80,7 @@ describe('MapKmlLayer', () => { .query(By.directive(MapKmlLayer))! .injector.get(MapKmlLayer); fixture.detectChanges(); + flush(); kmlLayerComponent.getDefaultViewport(); expect(kmlLayerSpy.getDefaultViewport).toHaveBeenCalled(); @@ -103,28 +107,30 @@ describe('MapKmlLayer', () => { kmlLayerSpy.getZIndex.and.returnValue(3); expect(kmlLayerComponent.getZIndex()).toBe(3); - }); + })); - it('initializes KmlLayer event handlers', () => { + it('initializes KmlLayer event handlers', fakeAsync(() => { const kmlLayerSpy = createKmlLayerSpy(DEFAULT_KML_OPTIONS); createKmlLayerConstructorSpy(kmlLayerSpy); const addSpy = kmlLayerSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('defaultviewport_changed', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('status_changed', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const kmlLayerSpy = createKmlLayerSpy(DEFAULT_KML_OPTIONS); createKmlLayerConstructorSpy(kmlLayerSpy); const addSpy = kmlLayerSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('defaultviewport_changed', jasmine.any(Function)); @@ -134,7 +140,7 @@ describe('MapKmlLayer', () => { expect(addSpy).toHaveBeenCalledWith('defaultviewport_changed', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -149,7 +155,6 @@ describe('MapKmlLayer', () => { `, imports: [GoogleMap, MapKmlLayer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapKmlLayer) kmlLayer!: MapKmlLayer; diff --git a/src/google-maps/map-kml-layer/map-kml-layer.ts b/src/google-maps/map-kml-layer/map-kml-layer.ts index ae8d1043bc23..b2459cd4c0e8 100644 --- a/src/google-maps/map-kml-layer/map-kml-layer.ts +++ b/src/google-maps/map-kml-layer/map-kml-layer.ts @@ -81,6 +81,9 @@ export class MapKmlLayer implements OnInit, OnDestroy { @Output() readonly kmlLayerInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._combineOptions() @@ -149,7 +152,7 @@ export class MapKmlLayer implements OnInit, OnDestroy { */ getStatus(): google.maps.KmlLayerStatus { this._assertInitialized(); - return this.kmlLayer.getStatus() as google.maps.KmlLayerStatus; + return this.kmlLayer.getStatus(); } /** diff --git a/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts b/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts index 7b1521977525..b028b355a3bf 100644 --- a/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts +++ b/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing'; import type {MarkerClusterer, Renderer, Algorithm} from './map-marker-clusterer-types'; import {DEFAULT_OPTIONS} from '../google-map/google-map'; @@ -46,7 +46,6 @@ describe('MapMarkerClusterer', () => { (window as any).markerClusterer = undefined; }); - // We can't test this easily without `fakeAsync`. it('throws an error if the clustering library has not been loaded', fakeAsync(() => { (window as any).markerClusterer = undefined; markerClustererConstructorSpy = createMarkerClustererConstructorSpy( @@ -60,10 +59,9 @@ describe('MapMarkerClusterer', () => { }).toThrowError(/MarkerClusterer class not found, cannot construct a marker cluster/); })); - it('initializes a Google Map Marker Clusterer', async () => { + it('initializes a Google Map Marker Clusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith({ map: mapSpy, @@ -71,14 +69,13 @@ describe('MapMarkerClusterer', () => { algorithm: undefined, onClusterClick: jasmine.any(Function), }); - }); + })); - it('sets marker clusterer inputs', async () => { + it('sets marker clusterer inputs', fakeAsync(() => { fixture.componentInstance.algorithm = {name: 'custom'} as any; fixture.componentInstance.renderer = {render: () => null!}; fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith({ map: mapSpy, @@ -86,14 +83,13 @@ describe('MapMarkerClusterer', () => { renderer: fixture.componentInstance.renderer, onClusterClick: jasmine.any(Function), }); - }); + })); - it('recreates the clusterer if the options change', async () => { + it('recreates the clusterer if the options change', fakeAsync(() => { fixture.componentInstance.algorithm = {name: 'custom1'} as any; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith({ map: mapSpy, @@ -105,8 +101,7 @@ describe('MapMarkerClusterer', () => { fixture.componentInstance.algorithm = {name: 'custom2'} as any; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererConstructorSpy).toHaveBeenCalledWith({ map: mapSpy, @@ -114,23 +109,21 @@ describe('MapMarkerClusterer', () => { renderer: undefined, onClusterClick: jasmine.any(Function), }); - }); + })); - it('sets Google Maps Markers in the MarkerClusterer', async () => { + it('sets Google Maps Markers in the MarkerClusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([ anyMarkerMatcher, anyMarkerMatcher, ]); - }); + })); - it('updates Google Maps Markers in the Marker Clusterer', async () => { + it('updates Google Maps Markers in the Marker Clusterer', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([ anyMarkerMatcher, @@ -140,8 +133,7 @@ describe('MapMarkerClusterer', () => { fixture.componentInstance.state = 'state2'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true); expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true); @@ -150,8 +142,7 @@ describe('MapMarkerClusterer', () => { fixture.componentInstance.state = 'state0'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([], true); expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith( @@ -159,38 +150,31 @@ describe('MapMarkerClusterer', () => { true, ); expect(markerClustererSpy.render).toHaveBeenCalledTimes(2); - }); + })); - it('initializes event handlers on the map related to clustering', async () => { + it('initializes event handlers on the map related to clustering', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(mapSpy.addListener).toHaveBeenCalledWith('clusteringbegin', jasmine.any(Function)); expect(mapSpy.addListener).not.toHaveBeenCalledWith('clusteringend', jasmine.any(Function)); - }); + })); - it('emits to clusterClick when the `onClusterClick` callback is invoked', async () => { + it('emits to clusterClick when the `onClusterClick` callback is invoked', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(fixture.componentInstance.onClusterClick).not.toHaveBeenCalled(); const callback = markerClustererConstructorSpy.calls.mostRecent().args[0].onClusterClick; callback({}, {}, {}); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); + flush(); expect(fixture.componentInstance.onClusterClick).toHaveBeenCalledTimes(1); - }); + })); }); -function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - @Component({ selector: 'test-app', imports: [GoogleMapsModule], @@ -213,7 +197,6 @@ function wait(milliseconds: number) { `, - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapMarkerClusterer) markerClusterer!: MapMarkerClusterer; diff --git a/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts b/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts index e217278af7f8..7e5e10b5061c 100644 --- a/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts +++ b/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, @@ -47,6 +48,7 @@ declare const markerClusterer: { @Component({ selector: 'map-marker-clusterer', exportAs: 'mapMarkerClusterer', + changeDetection: ChangeDetectionStrategy.OnPush, template: '', encapsulation: ViewEncapsulation.None, }) @@ -82,11 +84,7 @@ export class MapMarkerClusterer implements OnInit, OnChanges, OnDestroy { @Output() readonly clusteringend: Observable = this._closestMapEventManager.getLazyEmitter('clusteringend'); - /** - * Emits when a cluster has been clicked. - * Listening to this output will replace the default click handler on the cluster, - * disabling the default behavior of zooming into the markers in the cluster. - */ + /** Emits when a cluster has been clicked. */ @Output() readonly clusterClick: EventEmitter = new EventEmitter(); @@ -109,7 +107,7 @@ export class MapMarkerClusterer implements OnInit, OnChanges, OnDestroy { } } - async ngOnChanges(changes: SimpleChanges) { + async ngOnChanges(changes: SimpleChanges) { const change = changes['renderer'] || changes['algorithm']; // Since the options are set in the constructor, we have to recreate the cluster if they change. diff --git a/src/google-maps/map-marker/map-marker.spec.ts b/src/google-maps/map-marker/map-marker.spec.ts index e995ddbbdf37..96a1eeeecb31 100644 --- a/src/google-maps/map-marker/map-marker.spec.ts +++ b/src/google-maps/map-marker/map-marker.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import { @@ -22,12 +22,13 @@ describe('MapMarker', () => { (window.google as any) = undefined; }); - it('initializes a Google Map marker', () => { + it('initializes a Google Map marker', fakeAsync(() => { const markerSpy = createMarkerSpy(DEFAULT_MARKER_OPTIONS); const markerConstructorSpy = createMarkerConstructorSpy(markerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(markerConstructorSpy).toHaveBeenCalledWith({ ...DEFAULT_MARKER_OPTIONS, @@ -38,9 +39,9 @@ describe('MapMarker', () => { visible: undefined, map: mapSpy, }); - }); + })); - it('sets marker inputs', () => { + it('sets marker inputs', fakeAsync(() => { const options: google.maps.MarkerOptions = { position: {lat: 3, lng: 5}, title: 'marker title', @@ -61,11 +62,12 @@ describe('MapMarker', () => { fixture.componentInstance.icon = 'icon.png'; fixture.componentInstance.visible = false; fixture.detectChanges(); + flush(); expect(markerConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('sets marker options, ignoring map', () => { + it('sets marker options, ignoring map', fakeAsync(() => { const options: google.maps.MarkerOptions = { position: {lat: 3, lng: 5}, title: 'marker title', @@ -80,11 +82,12 @@ describe('MapMarker', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.options = options; fixture.detectChanges(); + flush(); expect(markerConstructorSpy).toHaveBeenCalledWith({...options, map: mapSpy}); - }); + })); - it('gives precedence to specific inputs over options', () => { + it('gives precedence to specific inputs over options', fakeAsync(() => { const options: google.maps.MarkerOptions = { position: {lat: 3, lng: 5}, title: 'marker title', @@ -111,17 +114,18 @@ describe('MapMarker', () => { fixture.componentInstance.clickable = expectedOptions.clickable!; fixture.componentInstance.options = options; fixture.detectChanges(); + flush(); expect(markerConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the marker', () => { + it('exposes methods that provide information about the marker', fakeAsync(() => { const markerSpy = createMarkerSpy(DEFAULT_MARKER_OPTIONS); createMarkerConstructorSpy(markerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); - + flush(); const marker = fixture.componentInstance.marker; markerSpy.getAnimation.and.returnValue(null); @@ -159,15 +163,16 @@ describe('MapMarker', () => { markerSpy.getZIndex.and.returnValue(2); expect(marker.getZIndex()).toBe(2); - }); + })); - it('initializes marker event handlers', () => { + it('initializes marker event handlers', fakeAsync(() => { const markerSpy = createMarkerSpy(DEFAULT_MARKER_OPTIONS); createMarkerConstructorSpy(markerSpy); const addSpy = markerSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('position_changed', jasmine.any(Function)); @@ -190,15 +195,16 @@ describe('MapMarker', () => { expect(addSpy).not.toHaveBeenCalledWith('title_changed', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('visible_changed', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('zindex_changed', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const markerSpy = createMarkerSpy(DEFAULT_MARKER_OPTIONS); createMarkerConstructorSpy(markerSpy); const addSpy = markerSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('flat_changed', jasmine.any(Function)); @@ -208,7 +214,7 @@ describe('MapMarker', () => { expect(addSpy).toHaveBeenCalledWith('flat_changed', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -228,7 +234,6 @@ describe('MapMarker', () => { `, imports: [GoogleMap, MapMarker], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapMarker) marker!: MapMarker; diff --git a/src/google-maps/map-marker/map-marker.ts b/src/google-maps/map-marker/map-marker.ts index 9aa999b0d3ff..5cbd1e962b8d 100644 --- a/src/google-maps/map-marker/map-marker.ts +++ b/src/google-maps/map-marker/map-marker.ts @@ -282,6 +282,9 @@ export class MapMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, */ marker?: google.maps.Marker; + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (!this._googleMap._isBrowser) { return; @@ -313,7 +316,7 @@ export class MapMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, }); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const {marker, _title, _position, _label, _clickable, _icon, _visible} = this; if (marker) { diff --git a/src/google-maps/map-polygon/map-polygon.spec.ts b/src/google-maps/map-polygon/map-polygon.spec.ts index 4ce8903ab89c..316be8393ecc 100644 --- a/src/google-maps/map-polygon/map-polygon.spec.ts +++ b/src/google-maps/map-polygon/map-polygon.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -35,18 +35,19 @@ describe('MapPolygon', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Polygon', () => { + it('initializes a Google Map Polygon', fakeAsync(() => { const polygonSpy = createPolygonSpy({}); const polygonConstructorSpy = createPolygonConstructorSpy(polygonSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(polygonConstructorSpy).toHaveBeenCalledWith({paths: undefined}); expect(polygonSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets path from input', () => { + it('sets path from input', fakeAsync(() => { const paths: google.maps.LatLngLiteral[] = [{lat: 3, lng: 5}]; const options: google.maps.PolygonOptions = {paths}; const polygonSpy = createPolygonSpy(options); @@ -55,11 +56,12 @@ describe('MapPolygon', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.paths = paths; fixture.detectChanges(); + flush(); expect(polygonConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('gives precedence to path input over options', () => { + it('gives precedence to path input over options', fakeAsync(() => { const paths: google.maps.LatLngLiteral[] = [{lat: 3, lng: 5}]; const expectedOptions: google.maps.PolygonOptions = {...polygonOptions, paths}; const polygonSpy = createPolygonSpy(expectedOptions); @@ -69,11 +71,12 @@ describe('MapPolygon', () => { fixture.componentInstance.options = polygonOptions; fixture.componentInstance.paths = paths; fixture.detectChanges(); + flush(); expect(polygonConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the Polygon', () => { + it('exposes methods that provide information about the Polygon', fakeAsync(() => { const polygonSpy = createPolygonSpy(polygonOptions); createPolygonConstructorSpy(polygonSpy); @@ -82,6 +85,7 @@ describe('MapPolygon', () => { .query(By.directive(MapPolygon))! .injector.get(MapPolygon); fixture.detectChanges(); + flush(); polygonSpy.getDraggable.and.returnValue(true); expect(polygonComponent.getDraggable()).toBe(true); @@ -97,15 +101,16 @@ describe('MapPolygon', () => { polygonSpy.getVisible.and.returnValue(true); expect(polygonComponent.getVisible()).toBe(true); - }); + })); - it('initializes Polygon event handlers', () => { + it('initializes Polygon event handlers', fakeAsync(() => { const polygonSpy = createPolygonSpy(polygonOptions); createPolygonConstructorSpy(polygonSpy); const addSpy = polygonSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function)); @@ -118,15 +123,16 @@ describe('MapPolygon', () => { expect(addSpy).not.toHaveBeenCalledWith('mouseover', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const polygonSpy = createPolygonSpy(polygonOptions); createPolygonConstructorSpy(polygonSpy); const addSpy = polygonSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function)); @@ -136,7 +142,7 @@ describe('MapPolygon', () => { expect(addSpy).toHaveBeenCalledWith('dragend', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -152,7 +158,6 @@ describe('MapPolygon', () => { `, imports: [GoogleMap, MapPolygon], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapPolygon) polygon!: MapPolygon; diff --git a/src/google-maps/map-polygon/map-polygon.ts b/src/google-maps/map-polygon/map-polygon.ts index 9297a3b5383c..007e9fac1627 100644 --- a/src/google-maps/map-polygon/map-polygon.ts +++ b/src/google-maps/map-polygon/map-polygon.ts @@ -139,6 +139,9 @@ export class MapPolygon implements OnInit, OnDestroy { @Output() readonly polygonInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._combineOptions() diff --git a/src/google-maps/map-polyline/map-polyline.spec.ts b/src/google-maps/map-polyline/map-polyline.spec.ts index 496df253eff0..26969f949d6c 100644 --- a/src/google-maps/map-polyline/map-polyline.spec.ts +++ b/src/google-maps/map-polyline/map-polyline.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -39,18 +39,19 @@ describe('MapPolyline', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Polyline', () => { + it('initializes a Google Map Polyline', fakeAsync(() => { const polylineSpy = createPolylineSpy({}); const polylineConstructorSpy = createPolylineConstructorSpy(polylineSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(polylineConstructorSpy).toHaveBeenCalledWith({path: undefined}); expect(polylineSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets path from input', () => { + it('sets path from input', fakeAsync(() => { const path: google.maps.LatLngLiteral[] = [{lat: 3, lng: 5}]; const options: google.maps.PolylineOptions = {path}; const polylineSpy = createPolylineSpy(options); @@ -59,11 +60,12 @@ describe('MapPolyline', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.path = path; fixture.detectChanges(); + flush(); expect(polylineConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('gives precedence to path input over options', () => { + it('gives precedence to path input over options', fakeAsync(() => { const path: google.maps.LatLngLiteral[] = [{lat: 3, lng: 5}]; const expectedOptions: google.maps.PolylineOptions = {...polylineOptions, path}; const polylineSpy = createPolylineSpy(expectedOptions); @@ -73,11 +75,12 @@ describe('MapPolyline', () => { fixture.componentInstance.options = polylineOptions; fixture.componentInstance.path = path; fixture.detectChanges(); + flush(); expect(polylineConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the Polyline', () => { + it('exposes methods that provide information about the Polyline', fakeAsync(() => { const polylineSpy = createPolylineSpy(polylineOptions); createPolylineConstructorSpy(polylineSpy); @@ -86,6 +89,7 @@ describe('MapPolyline', () => { .query(By.directive(MapPolyline))! .injector.get(MapPolyline); fixture.detectChanges(); + flush(); polylineSpy.getDraggable.and.returnValue(true); expect(polylineComponent.getDraggable()).toBe(true); @@ -98,15 +102,16 @@ describe('MapPolyline', () => { polylineSpy.getVisible.and.returnValue(true); expect(polylineComponent.getVisible()).toBe(true); - }); + })); - it('initializes Polyline event handlers', () => { + it('initializes Polyline event handlers', fakeAsync(() => { const polylineSpy = createPolylineSpy(polylineOptions); createPolylineConstructorSpy(polylineSpy); const addSpy = polylineSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('dblclick', jasmine.any(Function)); @@ -119,15 +124,16 @@ describe('MapPolyline', () => { expect(addSpy).not.toHaveBeenCalledWith('mouseover', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const polylineSpy = createPolylineSpy(polylineOptions); createPolylineConstructorSpy(polylineSpy); const addSpy = polylineSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function)); @@ -137,7 +143,7 @@ describe('MapPolyline', () => { expect(addSpy).toHaveBeenCalledWith('dragend', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -152,7 +158,6 @@ describe('MapPolyline', () => { `, imports: [GoogleMap, MapPolyline], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapPolyline) polyline!: MapPolyline; diff --git a/src/google-maps/map-polyline/map-polyline.ts b/src/google-maps/map-polyline/map-polyline.ts index 877913b0c60c..fc5c048dcfd3 100644 --- a/src/google-maps/map-polyline/map-polyline.ts +++ b/src/google-maps/map-polyline/map-polyline.ts @@ -137,6 +137,9 @@ export class MapPolyline implements OnInit, OnDestroy { @Output() readonly polylineInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._combineOptions() diff --git a/src/google-maps/map-rectangle/map-rectangle.spec.ts b/src/google-maps/map-rectangle/map-rectangle.spec.ts index 70f761085c53..830f2cddbb6e 100644 --- a/src/google-maps/map-rectangle/map-rectangle.spec.ts +++ b/src/google-maps/map-rectangle/map-rectangle.spec.ts @@ -1,5 +1,5 @@ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; @@ -31,18 +31,19 @@ describe('MapRectangle', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Rectangle', () => { + it('initializes a Google Map Rectangle', fakeAsync(() => { const rectangleSpy = createRectangleSpy({}); const rectangleConstructorSpy = createRectangleConstructorSpy(rectangleSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(rectangleConstructorSpy).toHaveBeenCalledWith({bounds: undefined}); expect(rectangleSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); - it('sets bounds from input', () => { + it('sets bounds from input', fakeAsync(() => { const bounds: google.maps.LatLngBoundsLiteral = {east: 3, north: 5, west: -3, south: -5}; const options: google.maps.RectangleOptions = {bounds}; const rectangleSpy = createRectangleSpy(options); @@ -51,11 +52,12 @@ describe('MapRectangle', () => { const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.bounds = bounds; fixture.detectChanges(); + flush(); expect(rectangleConstructorSpy).toHaveBeenCalledWith(options); - }); + })); - it('gives precedence to bounds input over options', () => { + it('gives precedence to bounds input over options', fakeAsync(() => { const bounds: google.maps.LatLngBoundsLiteral = {east: 3, north: 5, west: -3, south: -5}; const expectedOptions: google.maps.RectangleOptions = {...rectangleOptions, bounds}; const rectangleSpy = createRectangleSpy(expectedOptions); @@ -65,11 +67,12 @@ describe('MapRectangle', () => { fixture.componentInstance.options = rectangleOptions; fixture.componentInstance.bounds = bounds; fixture.detectChanges(); + flush(); expect(rectangleConstructorSpy).toHaveBeenCalledWith(expectedOptions); - }); + })); - it('exposes methods that provide information about the Rectangle', () => { + it('exposes methods that provide information about the Rectangle', fakeAsync(() => { const rectangleSpy = createRectangleSpy(rectangleOptions); createRectangleConstructorSpy(rectangleSpy); @@ -78,6 +81,7 @@ describe('MapRectangle', () => { .query(By.directive(MapRectangle))! .injector.get(MapRectangle); fixture.detectChanges(); + flush(); rectangleComponent.getBounds(); expect(rectangleSpy.getBounds).toHaveBeenCalled(); @@ -90,15 +94,16 @@ describe('MapRectangle', () => { rectangleSpy.getVisible.and.returnValue(true); expect(rectangleComponent.getVisible()).toBe(true); - }); + })); - it('initializes Rectangle event handlers', () => { + it('initializes Rectangle event handlers', fakeAsync(() => { const rectangleSpy = createRectangleSpy(rectangleOptions); createRectangleConstructorSpy(rectangleSpy); const addSpy = rectangleSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).toHaveBeenCalledWith('bounds_changed', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('click', jasmine.any(Function)); @@ -112,15 +117,16 @@ describe('MapRectangle', () => { expect(addSpy).not.toHaveBeenCalledWith('mouseover', jasmine.any(Function)); expect(addSpy).not.toHaveBeenCalledWith('mouseup', jasmine.any(Function)); expect(addSpy).toHaveBeenCalledWith('rightclick', jasmine.any(Function)); - }); + })); - it('should be able to add an event listener after init', () => { + it('should be able to add an event listener after init', fakeAsync(() => { const rectangleSpy = createRectangleSpy(rectangleOptions); createRectangleConstructorSpy(rectangleSpy); const addSpy = rectangleSpy.addListener; const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(addSpy).not.toHaveBeenCalledWith('dragend', jasmine.any(Function)); @@ -130,7 +136,7 @@ describe('MapRectangle', () => { expect(addSpy).toHaveBeenCalledWith('dragend', jasmine.any(Function)); subscription.unsubscribe(); - }); + })); }); @Component({ @@ -146,7 +152,6 @@ describe('MapRectangle', () => { `, imports: [GoogleMap, MapRectangle], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { @ViewChild(MapRectangle) rectangle!: MapRectangle; diff --git a/src/google-maps/map-rectangle/map-rectangle.ts b/src/google-maps/map-rectangle/map-rectangle.ts index e3444343c187..b8877bb21dc6 100644 --- a/src/google-maps/map-rectangle/map-rectangle.ts +++ b/src/google-maps/map-rectangle/map-rectangle.ts @@ -146,6 +146,9 @@ export class MapRectangle implements OnInit, OnDestroy { @Output() readonly rectangleInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._combineOptions() diff --git a/src/google-maps/map-traffic-layer/map-traffic-layer.spec.ts b/src/google-maps/map-traffic-layer/map-traffic-layer.spec.ts index da9b24618dff..5ee49b67aaa0 100644 --- a/src/google-maps/map-traffic-layer/map-traffic-layer.spec.ts +++ b/src/google-maps/map-traffic-layer/map-traffic-layer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import { @@ -24,16 +24,17 @@ describe('MapTrafficLayer', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Traffic Layer', () => { + it('initializes a Google Map Traffic Layer', fakeAsync(() => { const trafficLayerSpy = createTrafficLayerSpy(trafficLayerOptions); const trafficLayerConstructorSpy = createTrafficLayerConstructorSpy(trafficLayerSpy); const fixture = TestBed.createComponent(TestApp); fixture.componentInstance.autoRefresh = false; fixture.detectChanges(); + flush(); expect(trafficLayerConstructorSpy).toHaveBeenCalledWith(trafficLayerOptions); expect(trafficLayerSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); }); @Component({ @@ -44,7 +45,6 @@ describe('MapTrafficLayer', () => { `, imports: [GoogleMap, MapTrafficLayer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp { autoRefresh = false; diff --git a/src/google-maps/map-traffic-layer/map-traffic-layer.ts b/src/google-maps/map-traffic-layer/map-traffic-layer.ts index 8f29a32ed9fa..6427c763d260 100644 --- a/src/google-maps/map-traffic-layer/map-traffic-layer.ts +++ b/src/google-maps/map-traffic-layer/map-traffic-layer.ts @@ -55,6 +55,9 @@ export class MapTrafficLayer implements OnInit, OnDestroy { @Output() readonly trafficLayerInitialized: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (this._map._isBrowser) { this._combineOptions() diff --git a/src/google-maps/map-transit-layer/map-transit-layer.spec.ts b/src/google-maps/map-transit-layer/map-transit-layer.spec.ts index 4f3668d419cb..19fcd60f67ba 100644 --- a/src/google-maps/map-transit-layer/map-transit-layer.spec.ts +++ b/src/google-maps/map-transit-layer/map-transit-layer.spec.ts @@ -1,5 +1,5 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map'; import { @@ -23,16 +23,17 @@ describe('MapTransitLayer', () => { (window.google as any) = undefined; }); - it('initializes a Google Map Transit Layer', () => { + it('initializes a Google Map Transit Layer', fakeAsync(() => { const transitLayerSpy = createTransitLayerSpy(); const transitLayerConstructorSpy = createTransitLayerConstructorSpy(transitLayerSpy); const fixture = TestBed.createComponent(TestApp); fixture.detectChanges(); + flush(); expect(transitLayerConstructorSpy).toHaveBeenCalled(); expect(transitLayerSpy.setMap).toHaveBeenCalledWith(mapSpy); - }); + })); }); @Component({ @@ -43,6 +44,5 @@ describe('MapTransitLayer', () => { `, imports: [GoogleMap, MapTransitLayer], - changeDetection: ChangeDetectionStrategy.Eager, }) class TestApp {} diff --git a/src/google-maps/package.json b/src/google-maps/package.json index 68afc690a0e9..bef9fabe52d9 100644 --- a/src/google-maps/package.json +++ b/src/google-maps/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/angular/components/tree/main/src/google-maps#readme", "dependencies": { - "@types/google.maps": "^3.65.1", + "@types/google.maps": "^3.54.10", "tslib": "^2.3.0" }, "peerDependencies": { diff --git a/src/google-maps/schematics/BUILD.bazel b/src/google-maps/schematics/BUILD.bazel index ccde6ae12ad9..9ae5afbaa6e4 100644 --- a/src/google-maps/schematics/BUILD.bazel +++ b/src/google-maps/schematics/BUILD.bazel @@ -1,5 +1,5 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config") -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//tools:defaults.bzl", "npm_package", "ts_project") package(default_visibility = ["//visibility:public"]) diff --git a/src/google-maps/schematics/migration.json b/src/google-maps/schematics/migration.json index dc391493262e..8cf681dd7570 100644 --- a/src/google-maps/schematics/migration.json +++ b/src/google-maps/schematics/migration.json @@ -1,9 +1,9 @@ { "schematics": { - "migration-v22": { - "version": "22.0.0-0", - "description": "Updates the Angular Google Maps package to v22", - "factory": "./ng-update/index#updateToV22" + "migration-v21": { + "version": "21.0.0-0", + "description": "Updates the Angular Google Maps package to v21", + "factory": "./ng-update/index#updateToV21" } } } diff --git a/src/google-maps/schematics/ng-update/index.ts b/src/google-maps/schematics/ng-update/index.ts index 331fc9fb995e..3bca7b6e169f 100644 --- a/src/google-maps/schematics/ng-update/index.ts +++ b/src/google-maps/schematics/ng-update/index.ts @@ -8,7 +8,7 @@ import {Rule} from '@angular-devkit/schematics'; -/** Entry point for the migration schematics with target of Google Maps v22 */ -export function updateToV22(): Rule { +/** Entry point for the migration schematics with target of Angular Material v21 */ +export function updateToV21(): Rule { return () => {}; } diff --git a/src/google-maps/testing/fake-google-map-utils.ts b/src/google-maps/testing/fake-google-map-utils.ts index ef7ec9497920..2b9d132f0901 100644 --- a/src/google-maps/testing/fake-google-map-utils.ts +++ b/src/google-maps/testing/fake-google-map-utils.ts @@ -34,7 +34,9 @@ export interface TestingWindow extends Window { DirectionsRenderer?: jasmine.Spy; DirectionsService?: jasmine.Spy; LatLng?: jasmine.Spy; - visualization?: {}; + visualization?: { + HeatmapLayer?: jasmine.Spy; + }; marker?: { AdvancedMarkerElement?: jasmine.Spy; }; @@ -751,6 +753,48 @@ export function createDirectionsServiceConstructorSpy( return directionsServiceConstructorSpy; } +/** Creates a jasmine.SpyObj for a `google.maps.visualization.HeatmapLayer`. */ +export function createHeatmapLayerSpy(): jasmine.SpyObj { + const heatmapLayerSpy = jasmine.createSpyObj('google.maps.visualization.HeatmapLayer', [ + 'setMap', + 'setOptions', + 'setData', + 'getData', + ]); + return heatmapLayerSpy; +} + +/** + * Creates a jasmine.Spy to watch for the constructor + * of a `google.maps.visualization.HeatmapLayer`. + */ +export function createHeatmapLayerConstructorSpy( + heatmapLayerSpy: jasmine.SpyObj, +): jasmine.Spy { + // The spy target function cannot be an arrow-function as this breaks when created through `new`. + const heatmapLayerConstructorSpy = jasmine + .createSpy('HeatmapLayer constructor', function () { + return heatmapLayerSpy; + }) + .and.callThrough(); + const testingWindow: TestingWindow = window; + if (testingWindow.google && testingWindow.google.maps) { + if (!testingWindow.google.maps.visualization) { + testingWindow.google.maps.visualization = {}; + } + testingWindow.google.maps.visualization['HeatmapLayer'] = heatmapLayerConstructorSpy; + } else { + testingWindow.google = { + maps: { + visualization: { + 'HeatmapLayer': heatmapLayerConstructorSpy, + }, + }, + }; + } + return heatmapLayerConstructorSpy; +} + /** Creates a jasmine.SpyObj for a google.maps.LatLng. */ export function createLatLngSpy(): jasmine.SpyObj { return jasmine.createSpyObj('google.maps.LatLng', ['equals', 'lat', 'lng']); diff --git a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts index c278b20b28d2..3201880f33dc 100644 --- a/src/material-date-fns-adapter/adapter/date-fns-adapter.ts +++ b/src/material-date-fns-adapter/adapter/date-fns-adapter.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, inject} from '@angular/core'; +import {Injectable, inject} from '@angular/core'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; import { Locale, @@ -55,8 +55,10 @@ const DAY_OF_WEEK_FORMATS = { }; /** Adds date-fns support to Angular Material. */ -@Service({autoProvided: false}) +@Injectable() export class DateFnsAdapter extends DateAdapter { + constructor(...args: unknown[]); + constructor() { super(); const matDateLocale = inject(MAT_DATE_LOCALE, {optional: true}); diff --git a/src/material-date-fns-adapter/schematics/BUILD.bazel b/src/material-date-fns-adapter/schematics/BUILD.bazel index 52dbdc040b14..ef216aefaae4 100644 --- a/src/material-date-fns-adapter/schematics/BUILD.bazel +++ b/src/material-date-fns-adapter/schematics/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//tools:defaults.bzl", "npm_package", "ts_project") package(default_visibility = ["//visibility:public"]) diff --git a/src/material-experimental/README.md b/src/material-experimental/README.md index 2baa9a84d5fa..a188a1d82f49 100644 --- a/src/material-experimental/README.md +++ b/src/material-experimental/README.md @@ -30,16 +30,15 @@ component by following these steps: ... ``` -3. Import the component you want to use. For example, the checkbox: +3. Import the `NgModule` for the component you want to use. For example, the checkbox: ```ts - import {Component} from '@angular/core'; - import {MatCheckbox} from '@angular/material/checkbox'; + import {MatCheckboxModule} from '@angular/material/checkbox'; - @Component({ - imports: [MatCheckbox], - // ... + @NgModule({ + declarations: [MyComponent], + imports: [MatCheckboxModule], }) - export class MyComponent {} + export class MyModule {} ``` 4. Use the components just as you would the normal Angular Material components. For example, diff --git a/src/material-experimental/column-resize/column-resize.spec.ts b/src/material-experimental/column-resize/column-resize.spec.ts index e7ecfdd53d0d..6992936669c9 100644 --- a/src/material-experimental/column-resize/column-resize.spec.ts +++ b/src/material-experimental/column-resize/column-resize.spec.ts @@ -6,10 +6,10 @@ import { Component, Directive, ElementRef, - Service, + Injectable, ViewChild, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {MatTableModule} from '@angular/material/table'; import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs'; import {dispatchKeyboardEvent} from '../../cdk/testing/private'; @@ -277,8 +277,6 @@ abstract class BaseTestComponentRtl extends BaseTestComponent { @Component({ template: getTableTemplate(false), imports: [BidiModule, MatTableModule, MatColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeTest extends BaseTestComponent { @ViewChild(MatColumnResize) columnResize!: AbstractMatColumnResize; @@ -286,16 +284,14 @@ class MatResizeTest extends BaseTestComponent { @Component({ template: getTableTemplate(false), + changeDetection: ChangeDetectionStrategy.OnPush, imports: [BidiModule, MatTableModule, MatColumnResizeModule], - jit: true, // Due to dynamic template }) class MatResizeOnPushTest extends MatResizeTest {} @Component({ template: getTableTemplate(true), imports: [BidiModule, MatTableModule, MatDefaultEnabledColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeDefaultTest extends BaseTestComponent { @ViewChild(MatDefaultEnabledColumnResize) columnResize!: AbstractMatColumnResize; @@ -304,8 +300,6 @@ class MatResizeDefaultTest extends BaseTestComponent { @Component({ template: getTableTemplate(true), imports: [BidiModule, MatTableModule, MatDefaultEnabledColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeDefaultRtlTest extends BaseTestComponentRtl { @ViewChild(MatDefaultEnabledColumnResize) columnResize!: AbstractMatColumnResize; @@ -314,8 +308,6 @@ class MatResizeDefaultRtlTest extends BaseTestComponentRtl { @Component({ template: getFlexTemplate(false), imports: [BidiModule, MatTableModule, MatColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeFlexTest extends BaseTestComponent { @ViewChild(MatColumnResizeFlex) columnResize!: AbstractMatColumnResize; @@ -324,8 +316,6 @@ class MatResizeFlexTest extends BaseTestComponent { @Component({ template: getFlexTemplate(true), imports: [BidiModule, MatTableModule, MatDefaultEnabledColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeDefaultFlexTest extends BaseTestComponent { @ViewChild(MatDefaultEnabledColumnResizeFlex) @@ -335,8 +325,6 @@ class MatResizeDefaultFlexTest extends BaseTestComponent { @Component({ template: getFlexTemplate(true), imports: [BidiModule, MatTableModule, MatDefaultEnabledColumnResizeModule], - changeDetection: ChangeDetectionStrategy.Eager, - jit: true, // Due to dynamic template }) class MatResizeDefaultFlexRtlTest extends BaseTestComponentRtl { @ViewChild(MatDefaultEnabledColumnResizeFlex) @@ -363,12 +351,27 @@ class ElementDataSource extends DataSource { } // There's 1px of variance between different browsers in terms of positioning. -function expectApproximate(actual: number, expected: number, shouldBeApproximate = true) { - expect(actual === expected || actual === expected + 1 || actual === expected - 1) - .withContext( - `Expected ${actual}${shouldBeApproximate ? '' : ' not'} to be within 1 of ${expected}`, - ) - .toBe(shouldBeApproximate); +const approximateMatcher: jasmine.CustomMatcherFactories = { + isApproximately: () => ({ + compare: (actual: number, expected: number) => { + const result = { + pass: false, + message: `Expected ${actual} to be within 1 of ${expected}`, + }; + + result.pass = actual === expected || actual === expected + 1 || actual === expected - 1; + + return result; + }, + }), +}; + +interface NumberMatchers extends jasmine.Matchers { + isApproximately(expected: number): void; + not: NumberMatchers; +} +declare global { + function expect(actual: number): NumberMatchers; } const testCases = [ @@ -387,16 +390,15 @@ describe('Material Popover Edit', () => { let component: BaseTestComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(fakeAsync(() => { + jasmine.addMatchers(approximateMatcher); fixture = TestBed.createComponent(componentClass); component = fixture.componentInstance; fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); - fixture.detectChanges(); - }); + flush(); + })); - it('shows resize handle overlays on header row hover and while a resize handle is in use', async () => { + it('shows resize handle overlays on header row hover and while a resize handle is in use', fakeAsync(() => { expect(component.getOverlayThumbElement(0)).toBeUndefined(); const headerRowHeight = component.getHeaderRowHeight(); @@ -404,8 +406,7 @@ describe('Material Popover Edit', () => { component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); expect( component.getOverlayThumbElement(0).classList.contains('mat-column-resize-overlay-thumb'), @@ -414,11 +415,10 @@ describe('Material Popover Edit', () => { component.getOverlayThumbElement(2).classList.contains('mat-column-resize-overlay-thumb'), ).toBe(true); - expectApproximate(component.getOverlayThumbElement(0).offsetHeight, headerRowHeight); - expectApproximate(component.getOverlayThumbElement(2).offsetHeight, headerRowHeight); + expect(component.getOverlayThumbElement(0).offsetHeight).isApproximately(headerRowHeight); + expect(component.getOverlayThumbElement(2).offsetHeight).isApproximately(headerRowHeight); component.beginColumnResizeWithMouse(0); - fixture.detectChanges(); expect( component.getOverlayThumbElement(0).classList.contains('mat-column-resize-overlay-thumb'), @@ -427,20 +427,22 @@ describe('Material Popover Edit', () => { component.getOverlayThumbElement(2).classList.contains('mat-column-resize-overlay-thumb'), ).toBe(true); - expectApproximate(component.getOverlayThumbElement(0).offsetHeight, tableHeight); - expectApproximate(component.getOverlayThumbTopElement(0).offsetHeight, headerRowHeight); - expectApproximate(component.getOverlayThumbElement(2).offsetHeight, headerRowHeight); + expect(component.getOverlayThumbElement(0).offsetHeight).isApproximately(tableHeight); + expect(component.getOverlayThumbTopElement(0).offsetHeight).isApproximately( + headerRowHeight, + ); + expect(component.getOverlayThumbElement(2).offsetHeight).isApproximately(headerRowHeight); component.completeResizeWithMouseInProgress(0); component.endHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); + flush(); expect(component.getOverlayThumbElement(0)).toBeUndefined(); - }); + })); - it('resizes the target column via mouse input (live updates)', async () => { + it('resizes the target column via mouse input (live updates)', fakeAsync(() => { const initialTableWidth = component.getTableWidth(); const initialColumnWidth = component.getColumnWidth(1); const initialColumnPosition = component.getColumnOriginPosition(1); @@ -448,52 +450,49 @@ describe('Material Popover Edit', () => { component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.beginColumnResizeWithMouse(1); - fixture.detectChanges(); const initialThumbPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; let columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; // let nextColumnPositionDelta = // component.getColumnOriginPosition(2) - initialNextColumnPosition; - expectApproximate(thumbPositionDelta, columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // expAppexpectApproximateect(nextColumnPositionDelta, columnPositionDelta); + // expect(nextColumnPositionDelta).isApproximately(columnPositionDelta); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // expAppexpectApproximateect(component.getTableWidth(), initialTableWidth + 5); - expectApproximate(component.getColumnWidth(1), initialColumnWidth + 5); + // expect(component.getTableWidth()).isApproximately(initialTableWidth + 5); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 5); component.updateResizeWithMouseInProgress(1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; - expectApproximate(thumbPositionDelta, columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); - expectApproximate(component.getTableWidth(), initialTableWidth + 1); - expectApproximate(component.getColumnWidth(1), initialColumnWidth + 1); + expect(component.getTableWidth()).isApproximately(initialTableWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.completeResizeWithMouseInProgress(1); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); - expectApproximate(component.getColumnWidth(1), initialColumnWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.endHoverState(); fixture.detectChanges(); - }); + })); - it('resizes the target column via mouse input (no live update)', async () => { + it('resizes the target column via mouse input (no live update)', fakeAsync(() => { const initialTableWidth = component.getTableWidth(); const initialColumnWidth = component.getColumnWidth(1); @@ -501,23 +500,21 @@ describe('Material Popover Edit', () => { component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.beginColumnResizeWithMouse(1); - fixture.detectChanges(); const initialThumbPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; - expectApproximate(thumbPositionDelta, 5); + expect(thumbPositionDelta).isApproximately(5); expect(component.getColumnWidth(1)).toBe(initialColumnWidth); component.updateResizeWithMouseInProgress(1); fixture.detectChanges(); - await fixture.whenStable(); + flush(); thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; @@ -525,74 +522,67 @@ describe('Material Popover Edit', () => { expect(component.getColumnWidth(1)).toBe(initialColumnWidth); component.completeResizeWithMouseInProgress(1); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); - expectApproximate(component.getTableWidth(), initialTableWidth + 1); - expectApproximate(component.getColumnWidth(1), initialColumnWidth + 1); + expect(component.getTableWidth()).isApproximately(initialTableWidth + 1); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 1); component.endHoverState(); fixture.detectChanges(); - }); + })); - it('should not start dragging using the right mouse button', async () => { + it('should not start dragging using the right mouse button', fakeAsync(() => { const initialColumnWidth = component.getColumnWidth(1); component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.beginColumnResizeWithMouse(1, 2); - fixture.detectChanges(); const initialPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); - fixture.detectChanges(); expect(component.getOverlayThumbPosition(1)).toBe(initialPosition); expect(component.getColumnWidth(1)).toBe(initialColumnWidth); - }); + })); - it('cancels an active mouse resize with the escape key', async () => { + it('cancels an active mouse resize with the escape key', fakeAsync(() => { const initialTableWidth = component.getTableWidth(); const initialColumnWidth = component.getColumnWidth(1); const initialColumnPosition = component.getColumnOriginPosition(1); component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.beginColumnResizeWithMouse(1); - fixture.detectChanges(); const initialThumbPosition = component.getOverlayThumbPosition(1); component.updateResizeWithMouseInProgress(5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition; let columnPositionDelta = component.getColumnOriginPosition(1) - initialColumnPosition; - expectApproximate(thumbPositionDelta, columnPositionDelta); + expect(thumbPositionDelta).isApproximately(columnPositionDelta); - expectApproximate(component.getColumnWidth(1), initialColumnWidth + 5); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth + 5); // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. - // expAppexpectApproximateect(component.getTableWidth(), initialTableWidth + 5); + // expect(component.getTableWidth()).isApproximately(initialTableWidth + 5); dispatchKeyboardEvent(document, 'keyup', ESCAPE); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); - expectApproximate(component.getColumnWidth(1), initialColumnWidth); - expectApproximate(component.getTableWidth(), initialTableWidth); + expect(component.getColumnWidth(1)).isApproximately(initialColumnWidth); + expect(component.getTableWidth()).isApproximately(initialTableWidth); component.endHoverState(); fixture.detectChanges(); - }); + })); - it('notifies subscribers of a completed resize via ColumnResizeNotifier', async () => { + it('notifies subscribers of a completed resize via ColumnResizeNotifier', fakeAsync(() => { const initialColumnWidth = component.getColumnWidth(1); let resize: ColumnSize | null = null as ColumnSize | null; @@ -602,22 +592,21 @@ describe('Material Popover Edit', () => { component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); expect(resize).toBe(null); component.resizeColumnWithMouse(1, 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(resize).toEqual({columnId: 'name', size: initialColumnWidth + 5}); component.endHoverState(); fixture.detectChanges(); - }); + })); - it('does not notify subscribers of a canceled resize', async () => { + it('does not notify subscribers of a canceled resize', fakeAsync(() => { let resize: ColumnSize | null = null; component.columnResize.columnResizeNotifier.resizeCompleted.subscribe(size => { resize = size; @@ -625,35 +614,30 @@ describe('Material Popover Edit', () => { component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.beginColumnResizeWithMouse(0); - fixture.detectChanges(); component.updateResizeWithMouseInProgress(5); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); dispatchKeyboardEvent(document, 'keyup', ESCAPE); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); component.endHoverState(); fixture.detectChanges(); expect(resize).toBe(null); - }); + })); - it('performs a column resize triggered via ColumnResizeNotifier', async () => { + it('performs a column resize triggered via ColumnResizeNotifier', fakeAsync(() => { // Pre-verify that we are not updating the size to the initial size. - expectApproximate(component.getColumnWidth(1), 173, false); + expect(component.getColumnWidth(1)).not.isApproximately(173); component.columnResize.columnResizeNotifier.resize('name', 173); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); - expectApproximate(component.getColumnWidth(1), 173); - }); + expect(component.getColumnWidth(1)).isApproximately(173); + })); }); } @@ -662,7 +646,9 @@ describe('Material Popover Edit', () => { let fixture: ComponentFixture; let columnSizeStore: FakeColumnSizeStore; - beforeEach(async () => { + beforeEach(fakeAsync(() => { + jasmine.addMatchers(approximateMatcher); + TestBed.configureTestingModule({ providers: [ FakeColumnSizeStore, @@ -673,36 +659,32 @@ describe('Material Popover Edit', () => { component = fixture.componentInstance; columnSizeStore = TestBed.inject(FakeColumnSizeStore); fixture.detectChanges(); - await fixture.whenStable(); - await wait(50); - fixture.detectChanges(); - }); + flush(); + })); - it('applies the persisted size', async () => { - expectApproximate(component.getColumnWidth(1), 300, false); + it('applies the persisted size', fakeAsync(() => { + expect(component.getColumnWidth(1)).not.isApproximately(300); columnSizeStore.emitSize('theTable', 'name', 300); - fixture.detectChanges(); - await fixture.whenStable(); - expectApproximate(component.getColumnWidth(1), 300); - }); + flush(); + + expect(component.getColumnWidth(1)).isApproximately(300); + })); - it('persists the user-triggered size update', async () => { + it('persists the user-triggered size update', fakeAsync(() => { const initialColumnWidth = component.getColumnWidth(1); component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.resizeColumnWithMouse(1, 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); component.completeResizeWithMouseInProgress(1); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); component.endHoverState(); fixture.detectChanges(); @@ -711,26 +693,24 @@ describe('Material Popover Edit', () => { const {tableId, columnId, sizePx} = columnSizeStore.setSizeCalls[0]; expect(tableId).toBe('theTable'); expect(columnId).toBe('name'); - expectApproximate(sizePx, initialColumnWidth + 5); - }); + expect(sizePx).isApproximately(initialColumnWidth + 5); + })); - it('persists the user-triggered size update (live updates off)', async () => { + it('persists the user-triggered size update (live updates off)', fakeAsync(() => { const initialColumnWidth = component.getColumnWidth(1); component.columnResize.liveResizeUpdates = false; component.triggerHoverState(); fixture.detectChanges(); - await wait(250); - fixture.detectChanges(); + tick(200); component.resizeColumnWithMouse(1, 5); fixture.detectChanges(); - await fixture.whenStable(); + flush(); component.completeResizeWithMouseInProgress(1); - fixture.detectChanges(); - await fixture.whenStable(); + flush(); component.endHoverState(); fixture.detectChanges(); @@ -739,15 +719,11 @@ describe('Material Popover Edit', () => { const {tableId, columnId, sizePx} = columnSizeStore.setSizeCalls[0]; expect(tableId).toBe('theTable'); expect(columnId).toBe('name'); - expectApproximate(sizePx, initialColumnWidth + 5); - }); + expect(sizePx).isApproximately(initialColumnWidth + 5); + })); }); }); -function wait(milliseconds: number): Promise { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - function createElementData() { return [ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, @@ -758,7 +734,7 @@ function createElementData() { ]; } -@Service({autoProvided: false}) +@Injectable() class FakeColumnSizeStore extends ColumnSizeStore { readonly emitStore = new Map>(); readonly setSizeCalls: {tableId: string; columnId: string; sizePx: number}[] = []; diff --git a/src/material-experimental/column-resize/overlay-handle.ts b/src/material-experimental/column-resize/overlay-handle.ts index 22757d6b68bb..efbe94098f67 100644 --- a/src/material-experimental/column-resize/overlay-handle.ts +++ b/src/material-experimental/column-resize/overlay-handle.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ElementRef, NgZone, @@ -35,6 +36,7 @@ import {AbstractMatColumnResize} from './column-resize-directives/common'; * for handling column resize mouse events and displaying a vertical line along the column edge. */ @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {'class': 'mat-column-resize-overlay-thumb'}, template: '
    ', diff --git a/src/material-experimental/column-resize/resize-strategy.ts b/src/material-experimental/column-resize/resize-strategy.ts index 908b48d3f2db..a1c611b4966c 100644 --- a/src/material-experimental/column-resize/resize-strategy.ts +++ b/src/material-experimental/column-resize/resize-strategy.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, Provider} from '@angular/core'; +import {Injectable, Provider} from '@angular/core'; import { ResizeStrategy, @@ -19,7 +19,7 @@ export {TABLE_LAYOUT_FIXED_RESIZE_STRATEGY_PROVIDER}; /** * Overrides CdkFlexTableResizeStrategy to match mat-column elements. */ -@Service({autoProvided: false}) +@Injectable() export class MatFlexTableResizeStrategy extends CdkFlexTableResizeStrategy { protected override getColumnCssClass(cssFriendlyColumnName: string): string { return `mat-column-${cssFriendlyColumnName}`; diff --git a/src/material-experimental/menubar/menubar-item.spec.ts b/src/material-experimental/menubar/menubar-item.spec.ts index 840628c146d5..841cd1acdfe7 100644 --- a/src/material-experimental/menubar/menubar-item.spec.ts +++ b/src/material-experimental/menubar/menubar-item.spec.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {CdkMenuItem, CdkMenuModule, CdkMenu} from '@angular/cdk/menu'; import {MatMenuBarItem} from './menubar-item'; @@ -57,7 +57,6 @@ describe('MatMenuBarItem', () => { `, imports: [MatMenuBarModule, CdkMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMenuBarItem { @ViewChild(CdkMenuItem) menubarItem!: MatMenuBarItem; diff --git a/src/material-experimental/menubar/menubar-item.ts b/src/material-experimental/menubar/menubar-item.ts index d8c046139aa0..1b4a565a18e9 100644 --- a/src/material-experimental/menubar/menubar-item.ts +++ b/src/material-experimental/menubar/menubar-item.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core'; import {CdkMenuItem} from '@angular/cdk/menu'; /** Removes all icons from within the given element. */ @@ -27,6 +27,7 @@ function removeIcons(element: Element) { templateUrl: 'menubar-item.html', styleUrl: 'menubar-item.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { '[tabindex]': '_tabindex', 'type': 'button', diff --git a/src/material-experimental/menubar/menubar.spec.ts b/src/material-experimental/menubar/menubar.spec.ts index 8f923d492522..07c4511256a6 100644 --- a/src/material-experimental/menubar/menubar.spec.ts +++ b/src/material-experimental/menubar/menubar.spec.ts @@ -1,4 +1,4 @@ -import {Component, ViewChild, ElementRef, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild, ElementRef} from '@angular/core'; import {RIGHT_ARROW} from '@angular/cdk/keycodes'; import {CdkMenuBar} from '@angular/cdk/menu'; import {ComponentFixture, TestBed} from '@angular/core/testing'; @@ -50,7 +50,6 @@ describe('MatMenuBar', () => { `, imports: [MatMenuBarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatMenuBar { @ViewChild(CdkMenuBar) matMenubar!: MatMenuBar; diff --git a/src/material-experimental/menubar/menubar.ts b/src/material-experimental/menubar/menubar.ts index 26815641be86..fbb3da55ae22 100644 --- a/src/material-experimental/menubar/menubar.ts +++ b/src/material-experimental/menubar/menubar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {CDK_MENU, CdkMenuBar, CdkMenuGroup, MENU_STACK, MenuStack} from '@angular/cdk/menu'; /** @@ -19,6 +19,7 @@ import {CDK_MENU, CdkMenuBar, CdkMenuGroup, MENU_STACK, MenuStack} from '@angula templateUrl: 'menubar.html', styleUrl: 'menubar.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.mat-menubar]': 'true', }, diff --git a/src/material-experimental/popover-edit/popover-edit.spec.ts b/src/material-experimental/popover-edit/popover-edit.spec.ts index 74f9aac21a64..39002c3e6a3e 100644 --- a/src/material-experimental/popover-edit/popover-edit.spec.ts +++ b/src/material-experimental/popover-edit/popover-edit.spec.ts @@ -1,7 +1,7 @@ import {DataSource} from '@angular/cdk/collections'; import {DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes'; -import {Component, Directive, ElementRef, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, Directive, ElementRef, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {FormsModule, NgForm} from '@angular/forms'; import {MatTableModule} from '@angular/material/table'; import {BehaviorSubject} from 'rxjs'; @@ -82,13 +82,13 @@ abstract class BaseTestComponent { element.name = form.value['name']; } - async triggerHoverState(rowIndex = 0) { + triggerHoverState(rowIndex = 0) { const row = getRows(this.table.nativeElement)[rowIndex]; row.dispatchEvent(new Event('mouseover', {bubbles: true})); row.dispatchEvent(new Event('mousemove', {bubbles: true})); // Wait for the mouse hover debounce in edit-event-dispatcher. - await wait(200); + tick(41); } getRows() { @@ -123,12 +123,12 @@ abstract class BaseTestComponent { this.getOpenButton(rowIndex)!.click(); } - async openLens(rowIndex = 0, cellIndex = 1) { + openLens(rowIndex = 0, cellIndex = 1) { this.focusEditCell(rowIndex, cellIndex); this.getEditCell(rowIndex, cellIndex).dispatchEvent( new KeyboardEvent('keydown', {bubbles: true, key: 'Enter'}), ); - await wait(10); + flush(); } getEditPane() { @@ -230,7 +230,6 @@ class ElementDataSource extends DataSource { } `, imports: [MatTableModule, MatPopoverEditModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatFlexTableInCell extends BaseTestComponent { displayedColumns = ['before', 'name', 'weight']; @@ -283,7 +282,6 @@ class MatFlexTableInCell extends BaseTestComponent { } `, imports: [MatTableModule, MatPopoverEditModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTableInCell extends BaseTestComponent { displayedColumns = ['before', 'name', 'weight']; @@ -301,38 +299,38 @@ describe('Material Popover Edit', () => { let component: BaseTestComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(componentClass); component = fixture.componentInstance; fixture.detectChanges(); - await wait(50); + tick(10); fixture.detectChanges(); - }); + })); describe('row hover content', () => { - it('makes the first and last rows focusable but invisible', () => { + it('makes the first and last rows focusable but invisible', fakeAsync(() => { const rows = component.getRows(); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(rows.length - 1)).toBe( HoverContentState.FOCUSABLE, ); - }); + })); - it('shows and hides on-hover content only after a delay', async () => { + it('shows and hides on-hover content only after a delay', fakeAsync(() => { const [row0, row1] = component.getRows(); row0.dispatchEvent(new Event('mouseover', {bubbles: true})); row0.dispatchEvent(new Event('mousemove', {bubbles: true})); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); - await wait(20); + tick(20); row0.dispatchEvent(new Event('mousemove', {bubbles: true})); - await wait(20); + tick(20); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); - await wait(50); + tick(31); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.ON); @@ -342,111 +340,111 @@ describe('Material Popover Edit', () => { expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); - await wait(100); + tick(41); expect(component.hoverContentStateForRow(0)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.ON); - }); + })); - it('shows hover content for the focused row and makes the rows above and below focusable', async () => { + it('shows hover content for the focused row and makes the rows above and below focusable', fakeAsync(() => { expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(2); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(4); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.getEditCell(4).blur(); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); - }); + })); it( 'shows hover content for the editing row and makes the rows above and below ' + 'focusable unless focus is in a different table row in which case it takes priority', - async () => { + fakeAsync(() => { expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.FOCUSABLE); - await component.openLens(2); - await wait(50); + component.openLens(2); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(1)).toBe(HoverContentState.FOCUSABLE); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); component.focusEditCell(4); - await wait(50); + tick(1); expect(component.hoverContentStateForRow(2)).toBe(HoverContentState.OFF); expect(component.hoverContentStateForRow(4)).toBe(HoverContentState.ON); expect(component.hoverContentStateForRow(3)).toBe(HoverContentState.FOCUSABLE); - }, + }), ); }); describe('triggering edit', () => { - it('opens edit from on-hover button', async () => { - await component.triggerHoverState(); + it('opens edit from on-hover button', fakeAsync(() => { + component.triggerHoverState(); component.clickOpenButton(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('opens edit from Enter on focued cell', async () => { + it('opens edit from Enter on focued cell', fakeAsync(() => { // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not trigger edit when disabled', async () => { + it('does not trigger edit when disabled', fakeAsync(() => { component.nameEditDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('sets aria label and role dialog on the popup', async () => { + it('sets aria label and role dialog on the popup', fakeAsync(() => { component.nameEditAriaLabel = 'Label of name!!'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); // Uses Enter to open the lens. - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.lensIsOpen()).toBe(true); const dialogElem = component.getEditPane()!; expect(dialogElem.getAttribute('aria-label')).toBe('Label of name!!'); expect(dialogElem.getAttribute('role')).toBe('dialog'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); }); describe('focus manipulation', () => { @@ -537,54 +535,54 @@ describe('Material Popover Edit', () => { .querySelectorAll('input, button, .cdk-focus-trap-anchor'), ) as HTMLElement[]; - it('keeps focus within the lens by default', async () => { + it('keeps focus within the lens by default', fakeAsync(() => { // Open the name lens which has the default behavior. - await component.openLens(); + component.openLens(); const focusableElements = getFocusablePaneElements(); // Focus the last element (end focus trap anchor). focusableElements[focusableElements.length - 1].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the top of the lens. expect(document.activeElement).toBe(focusableElements[1]); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('moves focus to the next cell when focus leaves end of lens with matPopoverEditTabOut', async () => { + it('moves focus to the next cell when focus leaves end of lens with matPopoverEditTabOut', fakeAsync(() => { // Open the weight lens which has tab out behavior. - await component.openLens(0, 2); + component.openLens(0, 2); const focusableElements = getFocusablePaneElements(); // Focus the last element (end focus trap anchor). focusableElements[focusableElements.length - 1].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the next editable cell. expect(document.activeElement).toBe(component.getEditCell(1, 1)); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it(`moves focus to the previous cell when focus leaves end of lens with -matPopoverEditTabOut`, async () => { +matPopoverEditTabOut`, fakeAsync(() => { // Open the weight lens which has tab out behavior. - await component.openLens(0, 2); + component.openLens(0, 2); const focusableElements = getFocusablePaneElements(); // Focus the first (start focus trap anchor). focusableElements[0].focus(); - await fixture.whenStable(); + flush(); // Focus should have moved to the next editable cell. expect(document.activeElement).toBe(component.getEditCell(0, 1)); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); }); }); @@ -593,15 +591,15 @@ matPopoverEditTabOut`, async () => { expect(Math.floor(actual)).toBe(Math.floor(expected)); } - it('shows a lens with the value from the table', async () => { - await component.openLens(); + it('shows a lens with the value from the table', fakeAsync(() => { + component.openLens(); expect(component.getInput()!.value).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('positions the lens at the top left corner and spans the full width of the cell', async () => { - await component.openLens(); + it('positions the lens at the top left corner and spans the full width of the cell', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); const paneRect = component.getEditPane()!.getBoundingClientRect(); @@ -610,10 +608,10 @@ matPopoverEditTabOut`, async () => { expectPixelsToEqual(paneRect.width, cellRect.width); expectPixelsToEqual(paneRect.left, cellRect.left); expectPixelsToEqual(paneRect.top, cellRect.top); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('adjusts the positioning of the lens based on colspan', async () => { + it('adjusts the positioning of the lens based on colspan', fakeAsync(() => { const cellRects = getCells(getRows(component.table.nativeElement)[0]).map(cell => cell.getBoundingClientRect(), ); @@ -622,7 +620,7 @@ matPopoverEditTabOut`, async () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await component.openLens(); + component.openLens(); fixture.detectChanges(); let paneRect = component.getEditPane()!.getBoundingClientRect(); @@ -651,11 +649,11 @@ matPopoverEditTabOut`, async () => { // TODO: This was commented out after switching from the legacy table to the current // MDC-based table. This failed by being inaccurate by several pixels. // expectPixelsToEqual(paneRect.right, cellRects[2].right); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('updates the form and submits, closing the lens', async () => { - await component.openLens(); + it('updates the form and submits, closing the lens', fakeAsync(() => { + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -665,11 +663,11 @@ matPopoverEditTabOut`, async () => { expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydragon'); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on submit when form is invalid', async () => { - await component.openLens(); + it('does not close the lens on submit when form is invalid', fakeAsync(() => { + component.openLens(); component.getInput()!.value = ''; component.getInput()!.dispatchEvent(new Event('input')); @@ -677,15 +675,15 @@ matPopoverEditTabOut`, async () => { component.clickSubmitButton(); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'closes lens on submit when form is invalid with ' + 'matEditControlIgnoreSubmitUnlessValid = false', - async () => { + fakeAsync(() => { component.ignoreSubmitUnlessValid = false; - await component.openLens(); + component.openLens(); component.getInput()!.value = ''; component.getInput()!.dispatchEvent(new Event('input')); @@ -693,21 +691,21 @@ matPopoverEditTabOut`, async () => { component.clickSubmitButton(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); - it('closes the lens on close', async () => { - await component.openLens(); + it('closes the lens on close', fakeAsync(() => { + component.openLens(); component.clickCloseButton(); expect(component.lensIsOpen()).toBe(false); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes and reopens a lens with modified value persisted', async () => { - await component.openLens(); + it('closes and reopens a lens with modified value persisted', fakeAsync(() => { + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -718,15 +716,15 @@ matPopoverEditTabOut`, async () => { expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); expect(component.lensIsOpen()).toBe(false); - await component.openLens(); + component.openLens(); fixture.detectChanges(); expect(component.getInput()!.value).toBe('Hydragon'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('resets the lens to original value', async () => { - await component.openLens(); + it('resets the lens to original value', fakeAsync(() => { + component.openLens(); fixture.detectChanges(); component.getInput()!.value = 'Hydragon'; @@ -735,11 +733,11 @@ matPopoverEditTabOut`, async () => { component.clickRevertButton(); expect(component.getInput()!.value).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('resets the lens to previously submitted value', async () => { - await component.openLens(); + it('resets the lens to previously submitted value', fakeAsync(() => { + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -747,7 +745,7 @@ matPopoverEditTabOut`, async () => { component.clickSubmitButton(); fixture.detectChanges(); - await component.openLens(); + component.openLens(); fixture.detectChanges(); component.getInput()!.value = 'Hydragon X'; @@ -756,11 +754,11 @@ matPopoverEditTabOut`, async () => { component.clickRevertButton(); expect(component.getInput()!.value).toBe('Hydragon'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes the lens on escape', async () => { - await component.openLens(); + it('closes the lens on escape', fakeAsync(() => { + component.openLens(); const event = new KeyboardEvent('keydown', {bubbles: true, key: 'Escape'}); @@ -769,11 +767,11 @@ matPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(event.preventDefault).toHaveBeenCalled(); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on escape with a modifier key', async () => { - await component.openLens(); + it('does not close the lens on escape with a modifier key', fakeAsync(() => { + component.openLens(); const event = new KeyboardEvent('keydown', {bubbles: true, key: 'Escape'}); Object.defineProperty(event, 'altKey', {get: () => true}); @@ -783,20 +781,20 @@ matPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(true); expect(event.preventDefault).not.toHaveBeenCalled(); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('does not close the lens on click within lens', async () => { - await component.openLens(); + it('does not close the lens on click within lens', fakeAsync(() => { + component.openLens(); component.getInput()!.dispatchEvent(new Event('click', {bubbles: true})); expect(component.lensIsOpen()).toBe(true); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('closes the lens on outside click', async () => { - await component.openLens(); + it('closes the lens on outside click', fakeAsync(() => { + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -805,14 +803,14 @@ matPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'submits the lens on outside click with ' + 'matEditControlClickOutBehavior = "submit"', - async () => { + fakeAsync(() => { component.clickOutBehavior = 'submit'; - await component.openLens(); + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -821,15 +819,15 @@ matPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(false); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydragon'); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); it( 'does nothing on outside click with ' + 'matEditControlClickOutBehavior = "noop"', - async () => { + fakeAsync(() => { component.clickOutBehavior = 'noop'; - await component.openLens(); + component.openLens(); component.getInput()!.value = 'Hydragon'; component.getInput()!.dispatchEvent(new Event('input')); @@ -838,38 +836,38 @@ matPopoverEditTabOut`, async () => { expect(component.lensIsOpen()).toBe(true); expect(component.getEditCell().firstChild!.textContent!.trim()).toBe('Hydrogen'); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); - it('sets focus on the first input in the lens', async () => { - await component.openLens(); + it('sets focus on the first input in the lens', fakeAsync(() => { + component.openLens(); expect(document.activeElement).toBe(component.getInput()); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); - it('returns focus to the edited cell after closing', async () => { - await component.openLens(); + it('returns focus to the edited cell after closing', fakeAsync(() => { + component.openLens(); component.clickCloseButton(); expect(document.activeElement).toBe(component.getEditCell()); - await clearLeftoverTimers(); - }); + clearLeftoverTimers(); + })); it( 'does not focus to the edited cell after closing if another element ' + 'outside the lens is already focused', - async () => { - await component.openLens(0); + fakeAsync(() => { + component.openLens(0); component.getEditCell(1).focus(); component.getEditCell(1).dispatchEvent(new Event('click', {bubbles: true})); expect(document.activeElement).toBe(component.getEditCell(1)); - await clearLeftoverTimers(); - }, + clearLeftoverTimers(); + }), ); }); }); @@ -905,10 +903,6 @@ function getCells(row: Element): HTMLElement[] { // Common actions like mouse events and focus/blur cause timers to be fired off. // When not testing this behavior directly, use this function to clear any timers that were // created in passing. -async function clearLeftoverTimers() { - await wait(100); -} - -function wait(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); +function clearLeftoverTimers() { + tick(100); } diff --git a/src/material-experimental/selection/selection-column.ts b/src/material-experimental/selection/selection-column.ts index 0bfe04885c37..0b74ac1f965b 100644 --- a/src/material-experimental/selection/selection-column.ts +++ b/src/material-experimental/selection/selection-column.ts @@ -20,6 +20,7 @@ import { OnDestroy, OnInit, ViewChild, + ChangeDetectionStrategy, ViewEncapsulation, inject, } from '@angular/core'; @@ -56,6 +57,7 @@ import {MatSelectAll} from './select-all'; `, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrl: 'selection-column.css', encapsulation: ViewEncapsulation.None, imports: [ diff --git a/src/material-luxon-adapter/adapter/luxon-date-adapter.spec.ts b/src/material-luxon-adapter/adapter/luxon-date-adapter.spec.ts index a8873f36afd2..962ea66ee11f 100644 --- a/src/material-luxon-adapter/adapter/luxon-date-adapter.spec.ts +++ b/src/material-luxon-adapter/adapter/luxon-date-adapter.spec.ts @@ -418,22 +418,6 @@ describe('LuxonDateAdapter', () => { expect(clone.toISO()).toEqual(date.toISO()); }); - it('should respect timezone on clone', () => { - const dateLocal = DateTime.local(2017, JAN, 1); - const dateInCet = dateLocal.setZone('Europe/Budapest'); - const cloneInCet = adapter.clone(dateInCet); - const dateInPst = dateLocal.setZone('America/Los_Angeles'); - const cloneInPst = adapter.clone(dateInPst); - - expect(cloneInCet).not.toBe(dateInCet); - expect(cloneInCet.toISO()).toEqual(dateInCet.toISO()); - expect(cloneInCet.zone).toEqual(dateInCet.zone); - - expect(cloneInPst).not.toBe(dateInPst); - expect(cloneInPst.toISO()).toEqual(dateInPst.toISO()); - expect(cloneInPst.zone).toEqual(dateInPst.zone); - }); - it('should compare dates', () => { expect( adapter.compareDate(DateTime.local(2017, JAN, 1), DateTime.local(2017, JAN, 2)), diff --git a/src/material-luxon-adapter/adapter/luxon-date-adapter.ts b/src/material-luxon-adapter/adapter/luxon-date-adapter.ts index 4afd606ac727..b0a7b26cd805 100644 --- a/src/material-luxon-adapter/adapter/luxon-date-adapter.ts +++ b/src/material-luxon-adapter/adapter/luxon-date-adapter.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, InjectionToken, inject} from '@angular/core'; +import {Injectable, InjectionToken, inject} from '@angular/core'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; import { DateTime as LuxonDateTime, @@ -58,12 +58,14 @@ function range(length: number, valueFunction: (index: number) => T): T[] { } /** Adapts Luxon Dates for use with Angular Material. */ -@Service({autoProvided: false}) +@Injectable() export class LuxonDateAdapter extends DateAdapter { private _useUTC: boolean; private _firstDayOfWeek: number | undefined; private _defaultOutputCalendar: LuxonCalendarSystem; + constructor(...args: unknown[]); + constructor() { super(); @@ -134,10 +136,7 @@ export class LuxonDateAdapter extends DateAdapter { } clone(date: LuxonDateTime): LuxonDateTime { - return LuxonDateTime.fromObject(date.toObject(), { - ...this._getOptions(), - zone: date.zone, - }); + return LuxonDateTime.fromObject(date.toObject(), this._getOptions()); } createDate(year: number, month: number, date: number): LuxonDateTime { diff --git a/src/material-luxon-adapter/schematics/BUILD.bazel b/src/material-luxon-adapter/schematics/BUILD.bazel index 0517e558b56f..fe2f6bf496e0 100644 --- a/src/material-luxon-adapter/schematics/BUILD.bazel +++ b/src/material-luxon-adapter/schematics/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//tools:defaults.bzl", "npm_package", "ts_project") package(default_visibility = ["//visibility:public"]) diff --git a/src/material-moment-adapter/adapter/moment-date-adapter.ts b/src/material-moment-adapter/adapter/moment-date-adapter.ts index fc3018d7f151..1820657d2136 100644 --- a/src/material-moment-adapter/adapter/moment-date-adapter.ts +++ b/src/material-moment-adapter/adapter/moment-date-adapter.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service, InjectionToken, inject} from '@angular/core'; +import {Injectable, InjectionToken, inject} from '@angular/core'; import {DateAdapter, MAT_DATE_LOCALE} from '@angular/material/core'; // Depending on whether rollup is used, moment needs to be imported differently. // Since Moment.js doesn't have a default export, we normally need to import using the `* as` @@ -54,7 +54,7 @@ function range(length: number, valueFunction: (index: number) => T): T[] { } /** Adapts Moment.js Dates for use with Angular Material. */ -@Service({autoProvided: false}) +@Injectable() export class MomentDateAdapter extends DateAdapter { private _options = inject(MAT_MOMENT_DATE_ADAPTER_OPTIONS, { optional: true, @@ -75,6 +75,8 @@ export class MomentDateAdapter extends DateAdapter { narrowDaysOfWeek: string[]; }; + constructor(...args: unknown[]); + constructor() { super(); const dateLocale = inject(MAT_DATE_LOCALE, {optional: true}); diff --git a/src/material-moment-adapter/adapter/moment-locales.spec.ts b/src/material-moment-adapter/adapter/moment-locales.spec.ts deleted file mode 100644 index 4230be135e16..000000000000 --- a/src/material-moment-adapter/adapter/moment-locales.spec.ts +++ /dev/null @@ -1,2 +0,0 @@ -// This file provides an ambient module declaration for the untyped locales import. -declare module 'moment/min/locales'; diff --git a/src/material-moment-adapter/schematics/BUILD.bazel b/src/material-moment-adapter/schematics/BUILD.bazel index 6aefe0ea942f..08b55d8dcca8 100644 --- a/src/material-moment-adapter/schematics/BUILD.bazel +++ b/src/material-moment-adapter/schematics/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") load("//tools:defaults.bzl", "npm_package", "ts_project") package(default_visibility = ["//visibility:public"]) diff --git a/src/material/_index.scss b/src/material/_index.scss index dfdf3738c594..851e39654078 100644 --- a/src/material/_index.scss +++ b/src/material/_index.scss @@ -17,7 +17,6 @@ @forward './core/theming/theming' as private-* show private-clamp-density; @forward './core/typography/typography' show typography-hierarchy; @forward './core/typography/typography-utils' show font-shorthand; -@forward './core/theming/typography' as typography-*; @forward 'core/tokens/system' show system-level-colors, system-level-typography, system-level-elevation, system-level-shape, system-level-motion, system-level-state, theme, theme-overrides, m2-theme; @@ -38,7 +37,7 @@ @forward './core/core' show core, app-background, elevation-classes; @forward './core/ripple/ripple' show ripple; @forward './core/focus-indicators/private' show strong-focus-indicators, - strong-focus-indicators-color, strong-focus-indicators-theme, strong-focus-indicators-structure; + strong-focus-indicators-color, strong-focus-indicators-theme; @forward './core/style/elevation' show elevation, overridable-elevation, elevation-transition; // Theme bundles diff --git a/src/material/autocomplete/autocomplete-origin.ts b/src/material/autocomplete/autocomplete-origin.ts index daeeeb80e641..832570dd72cf 100644 --- a/src/material/autocomplete/autocomplete-origin.ts +++ b/src/material/autocomplete/autocomplete-origin.ts @@ -18,4 +18,7 @@ import {Directive, ElementRef, inject} from '@angular/core'; }) export class MatAutocompleteOrigin { elementRef = inject>(ElementRef); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index 5e9ad873463f..a5bfdb0386f3 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {addAriaReferencedId, removeAriaReferencedId} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import {DOWN_ARROW, ENTER, ESCAPE, TAB, UP_ARROW, hasModifierKey} from '@angular/cdk/keycodes'; import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; @@ -242,6 +243,9 @@ export class MatAutocompleteTrigger @Input({alias: 'matAutocompleteDisabled', transform: booleanAttribute}) autocompleteDisabled: boolean = false; + constructor(...args: unknown[]); + constructor() {} + /** Class to apply to the panel when it's above the input. */ private _aboveClass = 'mat-mdc-autocomplete-panel-above'; @@ -251,7 +255,7 @@ export class MatAutocompleteTrigger this._cleanupWindowBlur = this._renderer.listen('window', 'blur', this._windowBlurHandler); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (changes['position'] && this._positionStrategy) { this._setStrategyPositions(this._positionStrategy); @@ -268,6 +272,7 @@ export class MatAutocompleteTrigger this._componentDestroyed = true; this._destroyPanel(); this._closeKeyEventStream.complete(); + this._clearFromModal(); } /** Whether or not the autocomplete panel is open. */ @@ -325,6 +330,11 @@ export class MatAutocompleteTrigger // user clicks outside. this._changeDetectorRef.detectChanges(); } + + // Remove aria-owns attribute when the autocomplete is no longer visible. + if (this._trackedModal) { + removeAriaReferencedId(this._trackedModal, 'aria-owns', this.autocomplete.id); + } } /** @@ -749,6 +759,11 @@ export class MatAutocompleteTrigger private _openPanelInternal(valueOnAttach = this._element.nativeElement.value) { this._attachOverlay(valueOnAttach); this._floatLabel(); + // Add aria-owns attribute when the autocomplete becomes visible. + if (this._trackedModal) { + const panelId = this.autocomplete.id; + addAriaReferencedId(this._trackedModal, 'aria-owns', panelId); + } } private _attachOverlay(valueOnAttach: string): void { @@ -815,6 +830,7 @@ export class MatAutocompleteTrigger this.autocomplete._latestOpeningTrigger = this; this.autocomplete._setColor(this._formField?.color); this._updatePanelState(); + this._applyModalPanelOwnership(); // We need to do an extra `panelOpen` check in here, because the // autocomplete won't be shown if there are no options. @@ -1023,4 +1039,66 @@ export class MatAutocompleteTrigger } } } + + /** + * Track which modal we have modified the `aria-owns` attribute of. When the combobox trigger is + * inside an aria-modal, we apply aria-owns to the parent modal with the `id` of the options + * panel. Track the modal we have changed so we can undo the changes on destroy. + */ + private _trackedModal: Element | null = null; + + /** + * If the autocomplete trigger is inside of an `aria-modal` element, connect + * that modal to the options panel with `aria-owns`. + * + * For some browser + screen reader combinations, when navigation is inside + * of an `aria-modal` element, the screen reader treats everything outside + * of that modal as hidden or invisible. + * + * This causes a problem when the combobox trigger is _inside_ of a modal, because the + * options panel is rendered _outside_ of that modal, preventing screen reader navigation + * from reaching the panel. + * + * We can work around this issue by applying `aria-owns` to the modal with the `id` of + * the options panel. This effectively communicates to assistive technology that the + * options panel is part of the same interaction as the modal. + * + * At time of this writing, this issue is present in VoiceOver. + * See https://github.com/angular/components/issues/20694 + */ + private _applyModalPanelOwnership() { + // TODO(http://github.com/angular/components/issues/26853): consider de-duplicating this with + // the `LiveAnnouncer` and any other usages. + // + // Note that the selector here is limited to CDK overlays at the moment in order to reduce the + // section of the DOM we need to look through. This should cover all the cases we support, but + // the selector can be expanded if it turns out to be too narrow. + const modal = this._element.nativeElement.closest( + 'body > .cdk-overlay-container [aria-modal="true"]', + ); + + if (!modal) { + // Most commonly, the autocomplete trigger is not inside a modal. + return; + } + + const panelId = this.autocomplete.id; + + if (this._trackedModal) { + removeAriaReferencedId(this._trackedModal, 'aria-owns', panelId); + } + + addAriaReferencedId(modal, 'aria-owns', panelId); + this._trackedModal = modal; + } + + /** Clears the references to the listbox overlay element from the modal it was added to. */ + private _clearFromModal() { + if (this._trackedModal) { + const panelId = this.autocomplete.id; + + removeAriaReferencedId(this._trackedModal, 'aria-owns', panelId); + this._trackedModal = null; + } + } } diff --git a/src/material/autocomplete/autocomplete.md b/src/material/autocomplete/autocomplete.md index 8cda8f8874c6..12306f3f9b7d 100644 --- a/src/material/autocomplete/autocomplete.md +++ b/src/material/autocomplete/autocomplete.md @@ -16,7 +16,7 @@ track the value of the input. > Note: It is possible to use template-driven forms instead, if you prefer. We use reactive forms in this example because it makes subscribing to changes in the input's value easy. For this -example, be sure to import `ReactiveFormsModule` from `@angular/forms` in your component. +example, be sure to import `ReactiveFormsModule` from `@angular/forms` into your `NgModule`. If you are unfamiliar with using reactive forms, you can read more about the subject in the [Angular documentation](https://angular.dev/guide/forms/reactive-forms). diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index 916ea277b357..5445067f35f7 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -1,6 +1,6 @@ import {Direction} from '@angular/cdk/bidi'; import {DOWN_ARROW, ENTER, ESCAPE, SPACE, TAB, UP_ARROW} from '@angular/cdk/keycodes'; -import {createCloseScrollStrategy} from '@angular/cdk/overlay'; +import {OverlayModule, createCloseScrollStrategy} from '@angular/cdk/overlay'; import {_supportsShadowDom} from '@angular/cdk/platform'; import {ScrollDispatcher} from '@angular/cdk/scrolling'; import { @@ -16,6 +16,7 @@ import { import { ChangeDetectionStrategy, Component, + ElementRef, Injector, OnDestroy, OnInit, @@ -27,7 +28,14 @@ import { ViewEncapsulation, signal, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {AsyncPipe} from '@angular/common'; import {By} from '@angular/platform-browser'; @@ -76,10 +84,6 @@ describe('MatAutocomplete', () => { ); } - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - describe('panel toggling', () => { let fixture: ComponentFixture; let input: HTMLInputElement; @@ -109,7 +113,7 @@ describe('MatAutocomplete', () => { .toContain('California'); }); - it('should not open the panel on focus if the input is readonly', async () => { + it('should not open the panel on focus if the input is readonly', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.readOnly = true; fixture.detectChanges(); @@ -118,13 +122,13 @@ describe('MatAutocomplete', () => { .withContext('Expected panel state to start out closed.') .toBe(false); dispatchFakeEvent(input, 'focusin'); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - }); + })); - it('should not open using the arrow keys when the input is readonly', async () => { + it('should not open using the arrow keys when the input is readonly', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.readOnly = true; fixture.detectChanges(); @@ -133,11 +137,11 @@ describe('MatAutocomplete', () => { .withContext('Expected panel state to start out closed.') .toBe(false); dispatchKeyboardEvent(input, 'keydown', DOWN_ARROW); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - }); + })); it('should open the panel programmatically', () => { expect(fixture.componentInstance.trigger.panelOpen) @@ -158,12 +162,12 @@ describe('MatAutocomplete', () => { .toContain('California'); }); - it('should close the panel when the user clicks away', async () => { + it('should close the panel when the user clicks away', waitForAsync(async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); dispatchFakeEvent(document, 'click'); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) @@ -171,14 +175,14 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) .toBeFalsy(); - }); + })); - it('should close the panel when the user clicks away via auxiliary button', async () => { + it('should close the panel when the user clicks away via auxiliary button', waitForAsync(async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); dispatchFakeEvent(document, 'auxclick'); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) @@ -186,12 +190,12 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) .toBeFalsy(); - }); + })); - it('should close the panel when the user taps away on a touch device', async () => { + it('should close the panel when the user taps away on a touch device', fakeAsync(() => { dispatchFakeEvent(input, 'focus'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); dispatchFakeEvent(document, 'touchend'); expect(fixture.componentInstance.trigger.panelOpen) @@ -200,15 +204,17 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected tapping outside the panel to close the panel.`) .toBeFalsy(); - }); + })); - it('should close the panel when an option is clicked', () => { + it('should close the panel when an option is clicked', waitForAsync(async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking an option to set the panel state to closed.`) @@ -216,15 +222,17 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking an option to close the panel.`) .toBeFalsy(); - }); + })); - it('should close the panel when a newly created option is clicked', () => { + it('should close the panel when a newly created option is clicked', waitForAsync(async () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); // Filter down the option list to a subset of original options ('Alabama', 'California') typeInElement(input, 'al'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -237,10 +245,12 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'al'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[1].click(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking a new option to set the panel state to closed.`) @@ -248,15 +258,15 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected clicking a new option to close the panel.`) .toBeFalsy(); - }); + })); - it('should close the panel programmatically', async () => { + it('should close the panel programmatically', fakeAsync(() => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); fixture.componentInstance.trigger.closePanel(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected closing programmatically to set the panel state to closed.`) @@ -264,7 +274,7 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)) .withContext(`Expected closing programmatically to close the panel.`) .toBeFalsy(); - }); + })); it('should not throw when attempting to close the panel of a destroyed autocomplete', () => { const trigger = fixture.componentInstance.trigger; @@ -276,7 +286,7 @@ describe('MatAutocomplete', () => { expect(() => trigger.closePanel()).not.toThrow(); }); - it('should hide the panel when the options list is empty', async () => { + it('should hide the panel when the options list is empty', fakeAsync(() => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); @@ -291,20 +301,21 @@ describe('MatAutocomplete', () => { // Filter down the option list such that no options match the value typeInElement(input, 'af'); fixture.detectChanges(); - await wait(100); + tick(); fixture.detectChanges(); expect(panel.classList) .withContext(`Expected panel to hide itself when empty.`) .toContain('mat-mdc-autocomplete-hidden'); - }); + })); - it('should keep the label floating until the panel closes', () => { + it('should keep the label floating until the panel closes', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to float as soon as panel opens.') .toEqual('always'); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -316,7 +327,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to return to auto state after panel closes.') .toEqual('auto'); - }); + })); it('should not open the panel when the `input` event is invoked on a non-focused input', () => { expect(fixture.componentInstance.trigger.panelOpen) @@ -332,7 +343,7 @@ describe('MatAutocomplete', () => { .toBe(false); }); - it('should not mess with label placement if set to never', async () => { + it('should not mess with label placement if set to never', fakeAsync(() => { // TODO(crisbeto): this test likely doesn't make sense anymore. fixture.componentInstance.floatLabel = 'never' as any; fixture.changeDetectorRef.markForCheck(); @@ -342,7 +353,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay static.') .toEqual('never'); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -350,14 +361,14 @@ describe('MatAutocomplete', () => { ) as NodeListOf; options[1].click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay in static state after close.') .toEqual('never'); - }); + })); - it('should not mess with label placement if set to always', async () => { + it('should not mess with label placement if set to always', fakeAsync(() => { fixture.componentInstance.floatLabel = 'always'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -366,7 +377,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay elevated on open.') .toEqual('always'); - await fixture.whenStable(); + flush(); fixture.detectChanges(); const options = getOverlayHost(fixture)!.querySelectorAll( @@ -374,16 +385,16 @@ describe('MatAutocomplete', () => { ) as NodeListOf; options[1].click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.formField.floatLabel) .withContext('Expected label to stay elevated after close.') .toEqual('always'); - }); + })); - it('should toggle the visibility when typing and closing the panel', async () => { + it('should toggle the visibility when typing and closing the panel', fakeAsync(() => { fixture.componentInstance.trigger.openPanel(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) @@ -392,7 +403,7 @@ describe('MatAutocomplete', () => { typeInElement(input, 'x'); fixture.detectChanges(); - await wait(100); + tick(); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) @@ -408,13 +419,13 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'al'); fixture.detectChanges(); - await wait(100); + tick(); fixture.detectChanges(); expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) .withContext('Expected panel to be visible.') .toContain('mat-mdc-autocomplete-visible'); - }); + })); it('should animate the label when the input is focused', () => { const inputContainer = fixture.componentInstance.formField; @@ -426,19 +437,19 @@ describe('MatAutocomplete', () => { expect(inputContainer._animateAndLockLabel).toHaveBeenCalled(); }); - it('should provide the open state of the panel', async () => { + it('should provide the open state of the panel', fakeAsync(() => { expect(fixture.componentInstance.panel.isOpen).toBeFalsy( `Expected the panel to be unopened initially.`, ); dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.panel.isOpen).toBeTruthy( `Expected the panel to be opened on focus.`, ); - }); + })); it('should emit an event when the panel is opened', () => { fixture.componentInstance.trigger.openPanel(); @@ -458,7 +469,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.openedSpy).not.toHaveBeenCalled(); }); - it('should emit the `opened` event if the options come in after the panel is shown', async () => { + it('should emit the `opened` event if the options come in after the panel is shown', fakeAsync(() => { fixture.componentInstance.filteredStates = fixture.componentInstance.states = []; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -473,13 +484,13 @@ describe('MatAutocomplete', () => { ]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(100); + tick(); fixture.detectChanges(); expect(fixture.componentInstance.openedSpy).toHaveBeenCalled(); - }); + })); - it('should not emit the opened event multiple times while typing', async () => { + it('should not emit the opened event multiple times while typing', fakeAsync(() => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); @@ -487,11 +498,11 @@ describe('MatAutocomplete', () => { typeInElement(input, 'Alabam'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(fixture.componentInstance.openedSpy).toHaveBeenCalledTimes(1); - }); + })); it('should emit an event when the panel is closed', () => { fixture.componentInstance.trigger.openPanel(); @@ -555,11 +566,11 @@ describe('MatAutocomplete', () => { expect(input.hasAttribute('aria-haspopup')).toBe(false); }); - it('should reopen the panel when clicking on the input', async () => { + it('should reopen the panel when clicking on the input', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.focus(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -574,20 +585,21 @@ describe('MatAutocomplete', () => { expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); input.click(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(trigger.panelOpen).withContext('Expected panel to reopen on click.').toBe(true); - }); + })); }); - it('should not close the panel when clicking on the input', () => { + it('should not close the panel when clicking on the input', waitForAsync(async () => { const fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); const input = fixture.debugElement.query(By.css('input'))!.nativeElement; dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to be opened on focus.') @@ -599,9 +611,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to remain opened after clicking on the input.') .toBe(true); - }); + })); - it('should not close the panel when clicking on the input inside shadow DOM', () => { + it('should not close the panel when clicking on the input inside shadow DOM', waitForAsync(async () => { // This test is only relevant for Shadow DOM-capable browsers. if (!_supportsShadowDom()) { return; @@ -613,6 +625,7 @@ describe('MatAutocomplete', () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to be opened on focus.') @@ -624,7 +637,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext('Expected panel to remain opened after clicking on the input.') .toBe(true); - }); + })); it('should have the correct text direction in RTL', () => { const rtlFixture = createComponent(SimpleAutocomplete, [provideFakeDirectionality('rtl')]); @@ -675,19 +688,23 @@ describe('MatAutocomplete', () => { }).not.toThrow(); }); - it('should clear the selected option if it no longer matches the input text while typing', () => { + it('should clear the selected option if it no longer matches the input text while typing', waitForAsync(async () => { const fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); @@ -695,25 +712,30 @@ describe('MatAutocomplete', () => { input.value = ''; typeInElement(input, 'Ala'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(false); - }); + })); - it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', () => { + it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', waitForAsync(async () => { const fixture = createComponent(SimpleAutocomplete); fixture.componentInstance.requireSelection = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); @@ -721,9 +743,10 @@ describe('MatAutocomplete', () => { input.value = ''; typeInElement(input, 'Ala'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.options.first.selected).toBe(true); - }); + })); describe('forms integration', () => { let fixture: ComponentFixture; @@ -736,9 +759,10 @@ describe('MatAutocomplete', () => { input = fixture.debugElement.query(By.css('input'))!.nativeElement; }); - it('should update control value as user types with input value', () => { + it('should update control value as user types with input value', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); typeInElement(input, 'a'); fixture.detectChanges(); @@ -753,7 +777,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to be updated as user types.') .toEqual('al'); - }); + })); it('should update control value when autofilling', () => { // Simulate the browser autofilling the input by setting a value and @@ -768,9 +792,10 @@ describe('MatAutocomplete', () => { .toBe('Alabama'); }); - it('should update control value when option is selected with option value', () => { + it('should update control value when option is selected with option value', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -781,11 +806,12 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to equal the selected option value.') .toEqual({code: 'CA', name: 'California'}); - }); + })); - it('should update the control back to a string if user types after an option is selected', () => { + it('should update the control back to a string if user types after an option is selected', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -796,15 +822,17 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'Californi'); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.stateCtrl.value) .withContext('Expected control value to revert back to string.') .toEqual('Californi'); - }); + })); - it('should fill the text field with display value when an option is selected', () => { + it('should fill the text field with display value when an option is selected', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -815,11 +843,12 @@ describe('MatAutocomplete', () => { expect(input.value) .withContext(`Expected text field to fill with selected value.`) .toContain('California'); - }); + })); - it('should fill the text field with value if displayWith is not set', () => { + it('should fill the text field with value if displayWith is not set', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.componentInstance.panel.displayWith = null; fixture.componentInstance.options.toArray()[1].value = 'test value'; @@ -835,36 +864,37 @@ describe('MatAutocomplete', () => { expect(input.value) .withContext(`Expected input to fall back to selected option's value.`) .toContain('test value'); - }); + })); - it('should fill the text field correctly if value is set to obj programmatically', async () => { + it('should fill the text field correctly if value is set to obj programmatically', fakeAsync(() => { fixture.componentInstance.stateCtrl.setValue({code: 'AL', name: 'Alabama'}); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(input.value) .withContext(`Expected input to fill with matching option's viewValue.`) .toContain('Alabama'); - }); + })); - it('should clear the text field if value is reset programmatically', async () => { + it('should clear the text field if value is reset programmatically', fakeAsync(() => { typeInElement(input, 'Alabama'); fixture.detectChanges(); + tick(); fixture.componentInstance.stateCtrl.reset(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(input.value).withContext(`Expected input value to be empty after reset.`).toEqual(''); - }); + })); - it('should clear the previous selection when reactive form field is reset programmatically', async () => { + it('should clear the previous selection when reactive form field is reset programmatically', waitForAsync(async () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -879,14 +909,14 @@ describe('MatAutocomplete', () => { expect(option.selected).toBe(true); fixture.componentInstance.stateCtrl.reset(); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); - await fixture.whenStable(); + await new Promise(r => setTimeout(r)); expect(fixture.componentInstance.stateCtrl.value).toEqual(null); expect(option.selected).toBe(false); - }); + })); it('should disable input in view when disabled programmatically', () => { const formFieldElement = fixture.debugElement.query( @@ -924,13 +954,14 @@ describe('MatAutocomplete', () => { .toBe(true); }); - it('should mark the autocomplete control as dirty when an option is selected', () => { + it('should mark the autocomplete control as dirty when an option is selected', waitForAsync(async () => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to start out pristine.`) .toBe(false); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', @@ -941,7 +972,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to become dirty when an option was selected.`) .toBe(true); - }); + })); it('should not mark the control dirty when the value is set programmatically', () => { expect(fixture.componentInstance.stateCtrl.dirty) @@ -1018,7 +1049,7 @@ describe('MatAutocomplete', () => { let UP_ARROW_EVENT: KeyboardEvent; let ENTER_EVENT: KeyboardEvent; - beforeEach(async () => { + beforeEach(waitForAsync(async () => { fixture = createComponent(SimpleAutocomplete); fixture.detectChanges(); @@ -1029,7 +1060,8 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - }); + await new Promise(r => setTimeout(r)); + })); it('should not focus the option when DOWN key is pressed', () => { spyOn(fixture.componentInstance.options.first, 'focus'); @@ -1109,6 +1141,14 @@ describe('MatAutocomplete', () => { expect(optionEls[0].classList).toContain('mat-mdc-option-active'); }); + it('should set the active item properly after filtering', fakeAsync(() => { + const componentInstance = fixture.componentInstance; + + componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + })); + it('should set the active item properly after filtering', () => { const componentInstance = fixture.componentInstance; @@ -1129,30 +1169,30 @@ describe('MatAutocomplete', () => { expect(optionEls[1].classList).not.toContain('mat-mdc-option-active'); }); - it('should fill the text field when an option is selected with ENTER', async () => { + it('should fill the text field when an option is selected with ENTER', fakeAsync(() => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + flush(); fixture.detectChanges(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(input.value) .withContext(`Expected text field to fill with selected value on ENTER.`) .toContain('Alabama'); - }); + })); - it('should prevent the default enter key action', async () => { + it('should prevent the default enter key action', fakeAsync(() => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + flush(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); - await fixture.whenStable(); + flush(); expect(ENTER_EVENT.defaultPrevented) .withContext('Expected the default action to have been prevented.') .toBe(true); - }); + })); it('should not prevent the default enter action for a closed panel after a user action', () => { fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); @@ -1167,14 +1207,14 @@ describe('MatAutocomplete', () => { .toBe(false); }); - it('should not interfere with the ENTER key when pressing a modifier', async () => { + it('should not interfere with the ENTER key when pressing a modifier', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; expect(input.value).withContext('Expected input to start off blank.').toBeFalsy(); expect(trigger.panelOpen).withContext('Expected panel to start off open.').toBe(true); fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + flush(); fixture.detectChanges(); Object.defineProperty(ENTER_EVENT, 'altKey', {get: () => true}); @@ -1186,7 +1226,7 @@ describe('MatAutocomplete', () => { expect(ENTER_EVENT.defaultPrevented) .withContext('Expected the default ENTER action not to have been prevented.') .toBe(false); - }); + })); it('should fill the text field, not select an option, when SPACE is entered', () => { typeInElement(input, 'New'); @@ -1204,28 +1244,28 @@ describe('MatAutocomplete', () => { .toContain('New York'); }); - it('should mark the control dirty when selecting an option from the keyboard', async () => { + it('should mark the control dirty when selecting an option from the keyboard', fakeAsync(() => { expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to start out pristine.`) .toBe(false); fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + flush(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.stateCtrl.dirty) .withContext(`Expected control to become dirty when option was selected by ENTER.`) .toBe(true); - }); + })); - it('should open the panel again when typing after making a selection', async () => { + it('should open the panel again when typing after making a selection', fakeAsync(() => { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + flush(); fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read closed after ENTER key.`) @@ -1238,6 +1278,7 @@ describe('MatAutocomplete', () => { clearElement(input); typeInElement(input, 'Alabama'); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when typing in input.`) @@ -1245,14 +1286,15 @@ describe('MatAutocomplete', () => { expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when typing in input.`) .toContain('Alabama'); - }); + })); - it('should not open the panel if the `input` event was dispatched with changing the value', () => { + it('should not open the panel if the `input` event was dispatched with changing the value', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; dispatchFakeEvent(input, 'focusin'); typeInElement(input, 'A'); fixture.detectChanges(); + tick(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); @@ -1265,9 +1307,10 @@ describe('MatAutocomplete', () => { // to simulate what happen in some cases on IE. dispatchFakeEvent(input, 'input'); fixture.detectChanges(); + tick(); expect(trigger.panelOpen).withContext('Expected panel to stay closed.').toBe(false); - }); + })); it('should scroll to active options below the fold', () => { const trigger = fixture.componentInstance.trigger; @@ -1386,11 +1429,11 @@ describe('MatAutocomplete', () => { .toEqual(56); }); - it('should close the panel when pressing escape', async () => { + it('should close the panel when pressing escape', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.focus(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1398,13 +1441,13 @@ describe('MatAutocomplete', () => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') .toBe(input); expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); - }); + })); it('should prevent the default action when pressing escape', () => { const escapeEvent = dispatchKeyboardEvent(input, 'keydown', ESCAPE); @@ -1413,11 +1456,11 @@ describe('MatAutocomplete', () => { expect(escapeEvent.defaultPrevented).toBe(true); }); - it('should not close the panel when pressing escape with a modifier', async () => { + it('should not close the panel when pressing escape with a modifier', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.focus(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1425,7 +1468,6 @@ describe('MatAutocomplete', () => { const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE, undefined, {alt: true}); fixture.detectChanges(); - await fixture.whenStable(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') @@ -1434,15 +1476,15 @@ describe('MatAutocomplete', () => { expect(event.defaultPrevented) .withContext('Expected default action not to be prevented.') .toBe(false); - }); + })); - it('should close the panel when pressing ALT + UP_ARROW', async () => { + it('should close the panel when pressing ALT + UP_ARROW', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; const upArrowEvent = createKeyboardEvent('keydown', UP_ARROW, undefined, {alt: true}); spyOn(upArrowEvent, 'stopPropagation').and.callThrough(); input.focus(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(document.activeElement).withContext('Expected input to be focused.').toBe(input); @@ -1450,22 +1492,22 @@ describe('MatAutocomplete', () => { dispatchEvent(document.body, upArrowEvent); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected input to continue to be focused.') .toBe(input); expect(trigger.panelOpen).withContext('Expected panel to be closed.').toBe(false); expect(upArrowEvent.stopPropagation).toHaveBeenCalled(); - }); + })); - it('should close the panel when tabbing away from a trigger without results', async () => { + it('should close the panel when tabbing away from a trigger without results', fakeAsync(() => { fixture.componentInstance.states = []; fixture.componentInstance.filteredStates = []; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); input.focus(); - await fixture.whenStable(); + flush(); expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be rendered.') @@ -1473,18 +1515,18 @@ describe('MatAutocomplete', () => { dispatchKeyboardEvent(input, 'keydown', TAB); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be removed.') .toBeFalsy(); - }); + })); - it('should not close when a click event occurs on the outside while the panel has focus', async () => { + it('should not close when a click event occurs on the outside while the panel has focus', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; input.focus(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(document.activeElement).toBe(input, 'Expected input to be focused.'); @@ -1492,65 +1534,68 @@ describe('MatAutocomplete', () => { dispatchMouseEvent(document.body, 'click'); fixture.detectChanges(); - await fixture.whenStable(); expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.'); expect(trigger.panelOpen).toBe(true, 'Expected panel to stay open.'); - }); + })); - it('should reset the active option when closing with the escape key', async () => { + it('should reset the active option when closing with the escape key', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; trigger.openPanel(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); expect(!!trigger.activeOption).withContext('Expected no active option.').toBe(false); // Press the down arrow a few times. - for (const _ of [1, 2, 3]) { + [1, 2, 3].forEach(() => { trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + tick(); fixture.detectChanges(); - } + }); + // Note that this casts to a boolean, in order to prevent Jasmine + // from crashing when trying to stringify the option if the test fails. // Note that this casts to a boolean, in order to prevent Jasmine // from crashing when trying to stringify the option if the test fails. expect(!!trigger.activeOption).withContext('Expected to find an active option.').toBe(true); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - await fixture.whenStable(); + tick(); expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); - }); + })); - it('should reset the active option when closing by selecting with enter', async () => { + it('should reset the active option when closing by selecting with enter', fakeAsync(() => { const trigger = fixture.componentInstance.trigger; trigger.openPanel(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(trigger.panelOpen).withContext('Expected panel to be open.').toBe(true); expect(!!trigger.activeOption).withContext('Expected no active option.').toBe(false); // Press the down arrow a few times. - for (const _ of [1, 2, 3]) { + [1, 2, 3].forEach(() => { trigger._handleKeydown(DOWN_ARROW_EVENT); - await fixture.whenStable(); + tick(); fixture.detectChanges(); - } + }); + // Note that this casts to a boolean, in order to prevent Jasmine + // from crashing when trying to stringify the option if the test fails. // Note that this casts to a boolean, in order to prevent Jasmine // from crashing when trying to stringify the option if the test fails. expect(!!trigger.activeOption).withContext('Expected to find an active option.').toBe(true); trigger._handleKeydown(ENTER_EVENT); - await fixture.whenStable(); + tick(); expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); - }); + })); it('should not prevent the default action when a modifier key is pressed', () => { ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'].forEach(name => { @@ -1576,22 +1621,25 @@ describe('MatAutocomplete', () => { UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW); }); - it('should scroll to active options below the fold', () => { + it('should scroll to active options below the fold', waitForAsync(async () => { const fixture = createComponent(AutocompleteWithGroups); fixture.detectChanges(); fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); const container = document.querySelector('.mat-mdc-autocomplete-panel') as HTMLElement; fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + await new Promise(r => setTimeout(r)); fixture.detectChanges(); expect(container.scrollTop).withContext('Expected the panel not to scroll.').toBe(0); // Press the down arrow five times. for (const _unused of [1, 2, 3, 4, 5]) { fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + await new Promise(r => setTimeout(r)); } //
    `, imports: [MatCalendarBody], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardCalendarBody { label = 'Jan 2017'; @@ -743,7 +753,6 @@ class StandardCalendarBody { >
    `, imports: [MatCalendarBody], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangeCalendarBody { rows = createCalendarCells(4); diff --git a/src/material/datepicker/calendar-body.ts b/src/material/datepicker/calendar-body.ts index 73140921a559..791417ccd072 100644 --- a/src/material/datepicker/calendar-body.ts +++ b/src/material/datepicker/calendar-body.ts @@ -8,6 +8,7 @@ import {Platform} from '@angular/cdk/platform'; import { + ChangeDetectionStrategy, Component, ElementRef, EventEmitter, @@ -25,12 +26,13 @@ import { Renderer2, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; +import {NgClass} from '@angular/common'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {_StructuralStylesLoader} from '../core'; import {MatDatepickerIntl} from './datepicker-intl'; /** Extra CSS classes that can be associated with a calendar cell. */ -export type MatCalendarCellCssClasses = string | string[] | Set | Record; +export type MatCalendarCellCssClasses = string | string[] | Set | {[key: string]: any}; /** Function that can generate the extra classes that should be added to a calendar cell. */ export type MatCalendarCellClassFunction = ( @@ -46,19 +48,16 @@ let uniqueIdCounter = 0; */ export class MatCalendarCell { readonly id = uniqueIdCounter++; - readonly cssClasses: string | string[] | Record | undefined; constructor( public value: number, public displayValue: string, public ariaLabel: string, public enabled: boolean, - cssClasses?: MatCalendarCellCssClasses, + public cssClasses: MatCalendarCellCssClasses = {}, public compareValue = value, public rawValue?: D, - ) { - this.cssClasses = cssClasses instanceof Set ? Array.from(cssClasses) : cssClasses; - } + ) {} } /** Event emitted when a date inside the calendar is triggered as a result of a user action. */ @@ -95,6 +94,8 @@ const passiveEventOptions = {passive: true}; }, exportAs: 'matCalendarBody', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [NgClass], }) export class MatCalendarBody implements OnChanges, OnDestroy, AfterViewChecked { private _elementRef = inject>(ElementRef); @@ -222,6 +223,8 @@ export class MatCalendarBody implements OnChanges, OnDestroy, AfterView */ _trackRow = (row: MatCalendarCell[]) => row; + constructor(...args: unknown[]); + constructor() { const renderer = inject(Renderer2); const idGenerator = inject(_IdGenerator); @@ -280,7 +283,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy, AfterView return this.startValue === value || this.endValue === value; } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const columnChanges = changes['numCols']; const {rows, numCols} = this; diff --git a/src/material/datepicker/calendar-header.spec.ts b/src/material/datepicker/calendar-header.spec.ts index 0ff5aa7c293b..342a966b9616 100644 --- a/src/material/datepicker/calendar-header.spec.ts +++ b/src/material/datepicker/calendar-header.spec.ts @@ -1,5 +1,5 @@ import {provideFakeDirectionality} from '@angular/cdk/testing/private'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DateAdapter, provideNativeDateAdapter} from '../core'; @@ -379,7 +379,6 @@ describe('MatCalendarHeader', () => { (monthSelected)="selectedMonth=$event"> `, imports: [MatDatepickerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardCalendar { selected!: Date; @@ -397,7 +396,6 @@ class StandardCalendar { `, imports: [MatDatepickerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CalendarWithMinMaxDate { startAt = new Date(2018, JAN, 1); diff --git a/src/material/datepicker/calendar.spec.ts b/src/material/datepicker/calendar.spec.ts index eb5977bb3db6..9fae25266e1e 100644 --- a/src/material/datepicker/calendar.spec.ts +++ b/src/material/datepicker/calendar.spec.ts @@ -5,7 +5,7 @@ import { dispatchMouseEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {DateAdapter, provideNativeDateAdapter} from '../core'; @@ -672,7 +672,6 @@ describe('MatCalendar', () => { (monthSelected)="selectedMonth=$event"> `, imports: [MatCalendar], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardCalendar { selected!: Date; @@ -686,7 +685,6 @@ class StandardCalendar { `, imports: [MatCalendar], - changeDetection: ChangeDetectionStrategy.Eager, }) class CalendarWithMinMax { startAt!: Date; @@ -700,7 +698,6 @@ class CalendarWithMinMax { `, imports: [MatCalendar], - changeDetection: ChangeDetectionStrategy.Eager, }) class CalendarWithDateFilter { selected!: Date; @@ -721,7 +718,6 @@ class CalendarWithDateFilter { `, imports: [MatCalendar], - changeDetection: ChangeDetectionStrategy.Eager, }) class CalendarWithSelectableMinDate { startAt = new Date(2018, JUL, 0); diff --git a/src/material/datepicker/calendar.ts b/src/material/datepicker/calendar.ts index 67fe3ed0a75d..66f0b0760115 100644 --- a/src/material/datepicker/calendar.ts +++ b/src/material/datepicker/calendar.ts @@ -10,6 +10,7 @@ import {CdkPortalOutlet, ComponentPortal, ComponentType, Portal} from '@angular/ import { AfterContentInit, AfterViewChecked, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -56,6 +57,7 @@ export type MatCalendarView = 'month' | 'year' | 'multi-year'; templateUrl: 'calendar-header.html', exportAs: 'matCalendarHeader', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatButton, MatIconButton, MatTooltip], }) export class MatCalendarHeader { @@ -69,6 +71,8 @@ export class MatCalendarHeader { private _prevButtonLabel!: string; private _nextButtonLabel!: string; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); const changeDetectorRef = inject(ChangeDetectorRef); @@ -250,6 +254,7 @@ export class MatCalendarHeader { }, exportAs: 'matCalendar', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER], imports: [CdkPortalOutlet, CdkMonitorFocus, MatMonthView, MatYearView, MatMultiYearView], }) @@ -415,6 +420,8 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes */ readonly stateChanges = new Subject(); + constructor(...args: unknown[]); + constructor() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { @@ -452,24 +459,18 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes this.stateChanges.complete(); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { // Ignore date changes that are at a different time on the same day. This fixes issues where // the calendar re-renders when there is no meaningful change to [minDate] or [maxDate] // (#24435). const minDateChange: SimpleChange | undefined = changes['minDate'] && - !this._dateAdapter.sameDate( - changes['minDate'].previousValue as D | null, - changes['minDate'].currentValue as D | null, - ) + !this._dateAdapter.sameDate(changes['minDate'].previousValue, changes['minDate'].currentValue) ? changes['minDate'] : undefined; const maxDateChange: SimpleChange | undefined = changes['maxDate'] && - !this._dateAdapter.sameDate( - changes['maxDate'].previousValue as D | null, - changes['maxDate'].currentValue as D | null, - ) + !this._dateAdapter.sameDate(changes['maxDate'].previousValue, changes['maxDate'].currentValue) ? changes['maxDate'] : undefined; diff --git a/src/material/datepicker/date-range-input-parts.ts b/src/material/datepicker/date-range-input-parts.ts index 62c2b810088c..f5f29cdf1be7 100644 --- a/src/material/datepicker/date-range-input-parts.ts +++ b/src/material/datepicker/date-range-input-parts.ts @@ -30,7 +30,6 @@ import { ValidatorFn, Validators, } from '@angular/forms'; -import {FORM_FIELD} from '@angular/forms/signals'; import {ErrorStateMatcher, _ErrorStateTracker} from '../core'; import {_computeAriaAccessibleName} from './aria-accessible-name'; import {DateRange, DateSelectionModelChange} from './date-selection-model'; @@ -83,6 +82,8 @@ abstract class MatDateRangeInputPartBase this._errorStateTracker.errorState = value; } + constructor(...args: unknown[]); + constructor() { super(); @@ -104,11 +105,6 @@ abstract class MatDateRangeInputPartBase // everything has been resolved. const ngControl = this._injector.get(NgControl, null, {optional: true, self: true}); - this._errorStateTracker.formField = this._injector.get(FORM_FIELD, null, { - optional: true, - self: true, - }); - if (ngControl) { this.ngControl = ngControl; this._errorStateTracker.ngControl = ngControl; diff --git a/src/material/datepicker/date-range-input.spec.ts b/src/material/datepicker/date-range-input.spec.ts index 27e7717aa507..2eee25e8b69b 100644 --- a/src/material/datepicker/date-range-input.spec.ts +++ b/src/material/datepicker/date-range-input.spec.ts @@ -6,16 +6,8 @@ import { dispatchKeyboardEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import { - Component, - Directive, - ElementRef, - Provider, - Type, - ViewChild, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, Directive, ElementRef, Provider, Type, ViewChild} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import { FormControl, FormGroup, @@ -191,7 +183,7 @@ describe('MatDateRangeInput', () => { expect(rangeInput.getAttribute('aria-describedby')).toBe(labelId); }); - it('should preserve aria-describedby set directly in the DOM', () => { + it('should preserve aria-describedby set directly in the DOM', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); const rangeInput = fixture.nativeElement.querySelector('.mat-date-range-input'); @@ -201,7 +193,7 @@ describe('MatDateRangeInput', () => { const hint = fixture.nativeElement.querySelector('.mat-mdc-form-field-hint'); expect(rangeInput.getAttribute('aria-describedby')).toBe(`${hint.getAttribute('id')} custom`); - }); + })); it('should not set aria-labelledby if the form field does not have a label', () => { const fixture = createComponent(RangePickerNoLabel); @@ -212,7 +204,7 @@ describe('MatDateRangeInput', () => { expect(end.nativeElement.getAttribute('aria-labelledby')).toBeFalsy(); }); - it('should set aria-labelledby of the overlay to the form field label', () => { + it('should set aria-labelledby of the overlay to the form field label', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); fixture.detectChanges(); @@ -222,11 +214,12 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const popup = document.querySelector('.cdk-overlay-pane .mat-datepicker-content-container')!; expect(popup).toBeTruthy(); expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id')); - }); + })); it('should float the form field label when either input is focused', () => { const fixture = createComponent(StandardRangePicker); @@ -269,9 +262,10 @@ describe('MatDateRangeInput', () => { expect(rangeInput.empty).toBe(false); }); - it('should mark the range controls as invalid if the start value is after the end value', () => { + it('should mark the range controls as invalid if the start value is after the end value', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); fixture.detectChanges(); + tick(); const {start, end} = fixture.componentInstance.range.controls; // The default error state matcher only checks if the controls have been touched. @@ -295,7 +289,7 @@ describe('MatDateRangeInput', () => { expect(fixture.componentInstance.rangeInput.errorState).toBe(false); expect(start.errors?.['matStartDateInvalid']).toBeFalsy(); expect(end.errors?.['matEndDateInvalid']).toBeFalsy(); - }); + })); it('should pass the minimum date from the range input to the inner inputs', () => { const fixture = createComponent(StandardRangePicker); @@ -440,9 +434,10 @@ describe('MatDateRangeInput', () => { expect(startInput.focus).toHaveBeenCalled(); }); - it('should focus the end input when clicking on the form field when start has a value', () => { + it('should focus the end input when clicking on the form field when start has a value', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); fixture.detectChanges(); + tick(); const endInput = fixture.componentInstance.end.nativeElement; const formFieldContainer = fixture.nativeElement.querySelector('.mat-mdc-text-field-wrapper'); @@ -453,9 +448,10 @@ describe('MatDateRangeInput', () => { formFieldContainer.click(); fixture.detectChanges(); + tick(); expect(endInput.focus).toHaveBeenCalled(); - }); + })); it('should revalidate if a validation field changes', () => { const fixture = createComponent(StandardRangePicker); @@ -530,16 +526,18 @@ describe('MatDateRangeInput', () => { expect(end.nativeElement.getAttribute('max')).toContain('2020'); }); - it('should pass the range input value through to the calendar', () => { + it('should pass the range input value through to the calendar', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); const {start, end} = fixture.componentInstance.range.controls; const overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); start.setValue(new Date(2020, 1, 2)); end.setValue(new Date(2020, 1, 5)); fixture.detectChanges(); + tick(); fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const rangeTexts = Array.from( overlayContainerElement!.querySelectorAll( @@ -552,9 +550,9 @@ describe('MatDateRangeInput', () => { ).map(cell => cell.textContent!.trim()); expect(rangeTexts).toEqual(['2', '3', '4', '5']); - }); + })); - it("should have aria-desciredby on start and end date cells that point to the 's accessible name", () => { + it("should have aria-desciredby on start and end date cells that point to the 's accessible name", fakeAsync(() => { const fixture = createComponent(StandardRangePicker); const {start, end} = fixture.componentInstance.range.controls; const overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); @@ -562,9 +560,11 @@ describe('MatDateRangeInput', () => { start.setValue(new Date(2020, 1, 2)); end.setValue(new Date(2020, 1, 5)); fixture.detectChanges(); + tick(); fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const rangeStart = overlayContainerElement!.querySelector('.mat-calendar-body-range-start'); const rangeEnd = overlayContainerElement!.querySelector('.mat-calendar-body-range-end'); @@ -607,9 +607,9 @@ describe('MatDateRangeInput', () => { .join(' ') .trim(), ).toEqual('End date'); - }); + })); - it('should pass the comparison range through to the calendar', () => { + it('should pass the comparison range through to the calendar', fakeAsync(() => { const fixture = createComponent(StandardRangePicker); const overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); @@ -625,6 +625,7 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const rangeTexts = Array.from( overlayContainerElement!.querySelectorAll( @@ -637,9 +638,9 @@ describe('MatDateRangeInput', () => { ).map(cell => cell.textContent!.trim()); expect(rangeTexts).toEqual(['2', '3', '4', '5']); - }); + })); - it('should preserve the preselected values when assigning through ngModel', () => { + it('should preserve the preselected values when assigning through ngModel', fakeAsync(() => { const start = new Date(2020, 1, 2); const end = new Date(2020, 1, 2); const fixture = createComponent(RangePickerNgModel); @@ -647,17 +648,21 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.end = end; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + tick(); + fixture.detectChanges(); expect(fixture.componentInstance.start).toBe(start); expect(fixture.componentInstance.end).toBe(end); - }); + })); - it('should preserve the values when assigning both together through ngModel', () => { + it('should preserve the values when assigning both together through ngModel', fakeAsync(() => { const assignAndAssert = (start: Date, end: Date) => { fixture.componentInstance.start = start; fixture.componentInstance.end = end; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + tick(); + fixture.detectChanges(); expect(fixture.componentInstance.start).toBe(start); expect(fixture.componentInstance.end).toBe(end); }; @@ -667,39 +672,42 @@ describe('MatDateRangeInput', () => { assignAndAssert(new Date(2020, 1, 2), new Date(2020, 1, 5)); assignAndAssert(new Date(2020, 2, 2), new Date(2020, 2, 5)); - }); + })); - it('should not be dirty on init when there is no value', () => { + it('should not be dirty on init when there is no value', fakeAsync(() => { const fixture = createComponent(RangePickerNgModel); fixture.detectChanges(); + flush(); const {startModel, endModel} = fixture.componentInstance; expect(startModel.dirty).toBe(false); expect(startModel.touched).toBe(false); expect(endModel.dirty).toBe(false); expect(endModel.touched).toBe(false); - }); + })); - it('should not be dirty on init when there is a value', () => { + it('should not be dirty on init when there is a value', fakeAsync(() => { const fixture = createComponent(RangePickerNgModel); fixture.componentInstance.start = new Date(2020, 1, 2); fixture.componentInstance.end = new Date(2020, 2, 2); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); const {startModel, endModel} = fixture.componentInstance; expect(startModel.dirty).toBe(false); expect(startModel.touched).toBe(false); expect(endModel.dirty).toBe(false); expect(endModel.touched).toBe(false); - }); + })); - it('should mark the input as dirty once the user types in it', () => { + it('should mark the input as dirty once the user types in it', fakeAsync(() => { const fixture = createComponent(RangePickerNgModel); fixture.componentInstance.start = new Date(2020, 1, 2); fixture.componentInstance.end = new Date(2020, 2, 2); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); const {startModel, endModel, startInput, endInput} = fixture.componentInstance; expect(startModel.dirty).toBe(false); @@ -708,6 +716,8 @@ describe('MatDateRangeInput', () => { endInput.nativeElement.value = '30/12/2020'; dispatchFakeEvent(endInput.nativeElement, 'input'); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(startModel.dirty).toBe(false); expect(endModel.dirty).toBe(true); @@ -715,14 +725,17 @@ describe('MatDateRangeInput', () => { startInput.nativeElement.value = '12/12/2020'; dispatchFakeEvent(startInput.nativeElement, 'input'); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(startModel.dirty).toBe(true); expect(endModel.dirty).toBe(true); - }); + })); - it('should mark both inputs as touched when the range picker is closed', () => { + it('should mark both inputs as touched when the range picker is closed', fakeAsync(() => { const fixture = createComponent(RangePickerNgModel); fixture.detectChanges(); + flush(); const {startModel, endModel, rangePicker} = fixture.componentInstance; expect(startModel.dirty).toBe(false); @@ -732,6 +745,8 @@ describe('MatDateRangeInput', () => { rangePicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(startModel.dirty).toBe(false); expect(startModel.touched).toBe(false); @@ -740,12 +755,13 @@ describe('MatDateRangeInput', () => { rangePicker.close(); fixture.detectChanges(); + flush(); expect(startModel.dirty).toBe(false); expect(startModel.touched).toBe(true); expect(endModel.dirty).toBe(false); expect(endModel.touched).toBe(true); - }); + })); it('should move focus to the start input when pressing backspace on an empty end input', () => { const fixture = createComponent(StandardRangePicker); @@ -1034,21 +1050,24 @@ describe('MatDateRangeInput', () => { expect(endInput.errorStateMatcher).toBe(matcher); }); - it('should only update model for input that changed', () => { + it('should only update model for input that changed', fakeAsync(() => { const fixture = createComponent(RangePickerNgModel); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.startDateModelChangeCount).toBe(0); expect(fixture.componentInstance.endDateModelChangeCount).toBe(0); fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const fromDate = new Date(2020, 0, 1); const toDate = new Date(2020, 0, 2); fixture.componentInstance.rangePicker.select(fromDate); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.startDateModelChangeCount) .withContext('Start Date set once') @@ -1059,6 +1078,7 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.rangePicker.select(toDate); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.startDateModelChangeCount) .withContext('Start Date unchanged (set once)') @@ -1069,11 +1089,13 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.rangePicker.open(); fixture.detectChanges(); + tick(); const fromDate2 = new Date(2021, 0, 1); const toDate2 = new Date(2021, 0, 2); fixture.componentInstance.rangePicker.select(fromDate2); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.startDateModelChangeCount) .withContext('Start Date set twice') @@ -1084,6 +1106,7 @@ describe('MatDateRangeInput', () => { fixture.componentInstance.rangePicker.select(toDate2); fixture.detectChanges(); + tick(); expect(fixture.componentInstance.startDateModelChangeCount) .withContext('Start Date unchanged (set twice)') @@ -1091,7 +1114,7 @@ describe('MatDateRangeInput', () => { expect(fixture.componentInstance.endDateModelChangeCount) .withContext('End date set three times') .toBe(3); - }); + })); it('should mark the range picker as required when the entire group has the required validator', () => { const fixture = createComponent(StandardRangePicker); @@ -1157,7 +1180,6 @@ describe('MatDateRangeInput', () => { MatDateRangePicker, ReactiveFormsModule, ], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardRangePicker { @ViewChild('start') start!: ElementRef; @@ -1192,7 +1214,6 @@ class StandardRangePicker { `, imports: [MatDateRangeInput, MatEndDate, MatFormField, MatDateRangePicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerNoStart {} @@ -1207,7 +1228,6 @@ class RangePickerNoStart {} `, imports: [MatDateRangeInput, MatStartDate, MatFormField, MatDateRangePicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerNoEnd {} @@ -1230,7 +1250,6 @@ class RangePickerNoEnd {} MatDateRangePicker, FormsModule, ], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerNgModel { @ViewChild(MatStartDate, {read: NgModel}) startModel!: NgModel; @@ -1270,7 +1289,6 @@ class RangePickerNgModel { `, imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerNoLabel { @ViewChild('start') start!: ElementRef; @@ -1311,7 +1329,6 @@ class CustomValidator implements Validator { CustomValidator, FormsModule, ], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerWithCustomValidator { @ViewChild(CustomValidator) validator!: CustomValidator; @@ -1333,7 +1350,6 @@ class RangePickerWithCustomValidator { `, imports: [MatDateRangeInput, MatStartDate, MatEndDate, MatFormField, MatDateRangePicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class RangePickerErrorStateMatcher { @ViewChild(MatStartDate) startInput!: MatStartDate; diff --git a/src/material/datepicker/date-range-input.ts b/src/material/datepicker/date-range-input.ts index f2486041beb2..0c2df0db7761 100644 --- a/src/material/datepicker/date-range-input.ts +++ b/src/material/datepicker/date-range-input.ts @@ -9,6 +9,7 @@ import {_IdGenerator, CdkMonitorFocus, FocusOrigin} from '@angular/cdk/a11y'; import { AfterContentInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -49,6 +50,7 @@ import {DateFilterFn, _MatFormFieldPartial, dateInputsHaveChanged} from './datep // `aria-owns` for this, because it's only defined while the calendar is open. '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null', }, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [{provide: MatFormFieldControl, useExisting: MatDateRangeInput}], imports: [CdkMonitorFocus], @@ -261,6 +263,8 @@ export class MatDateRangeInput */ readonly disableAutomaticLabeling = true; + constructor(...args: unknown[]); + constructor() { if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); @@ -335,7 +339,7 @@ export class MatDateRangeInput }); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (dateInputsHaveChanged(changes, this._dateAdapter)) { this.stateChanges.next(undefined); } diff --git a/src/material/datepicker/date-range-picker.ts b/src/material/datepicker/date-range-picker.ts index 7d094e7d7536..b9d8326e255c 100644 --- a/src/material/datepicker/date-range-picker.ts +++ b/src/material/datepicker/date-range-picker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, inject, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, ViewEncapsulation} from '@angular/core'; import {MatDatepickerBase, MatDatepickerContent, MatDatepickerControl} from './datepicker-base'; import {MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER, DateRange} from './date-selection-model'; import { @@ -34,6 +34,7 @@ export interface MatDateRangePickerInput extends MatDatepickerControl { selector: 'mat-date-range-picker', template: '', exportAs: 'matDateRangePicker', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER, diff --git a/src/material/datepicker/datepicker-actions.spec.ts b/src/material/datepicker/datepicker-actions.spec.ts index 5959c4e28d8f..e160ffb4fc34 100644 --- a/src/material/datepicker/datepicker-actions.spec.ts +++ b/src/material/datepicker/datepicker-actions.spec.ts @@ -1,5 +1,5 @@ -import {Component, ElementRef, Type, ViewChild, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ElementRef, Type, ViewChild} from '@angular/core'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../core'; import {MatInputModule} from '../input'; @@ -19,38 +19,43 @@ describe('MatDatepickerActions', () => { return TestBed.createComponent(component); } - it('should render the actions inside calendar panel in popup mode', () => { + it('should render the actions inside calendar panel in popup mode', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const actions = document.querySelector('.mat-datepicker-content .mat-datepicker-actions'); expect(actions).toBeTruthy(); expect(actions?.querySelector('.cancel')).toBeTruthy(); expect(actions?.querySelector('.apply')).toBeTruthy(); - }); + })); - it('should render the actions inside calendar panel in touch UI mode', () => { + it('should render the actions inside calendar panel in touch UI mode', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.componentInstance.touchUi = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const actions = document.querySelector('.mat-datepicker-content .mat-datepicker-actions'); expect(actions).toBeTruthy(); expect(actions?.querySelector('.cancel')).toBeTruthy(); expect(actions?.querySelector('.apply')).toBeTruthy(); - }); + })); - it('should not assign the value or close the datepicker when a value is selected', () => { + it('should not assign the value or close the datepicker when a value is selected', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); const {control, datepicker, onDateChange, input} = fixture.componentInstance; datepicker.open(); fixture.detectChanges(); + tick(); const content = document.querySelector('.mat-datepicker-content')!; const cells = content.querySelectorAll('.mat-calendar-body-cell'); @@ -63,20 +68,23 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + flush(); expect(datepicker.opened).toBe(true); expect(input.nativeElement.value).toBeFalsy(); expect(control.value).toBeFalsy(); expect(onDateChange).not.toHaveBeenCalled(); expect(content.querySelector('.mat-calendar-body-selected')).toBeTruthy(); - }); + })); - it('should close without changing the value when clicking on the cancel button', () => { + it('should close without changing the value when clicking on the cancel button', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); const {control, datepicker, onDateChange, input} = fixture.componentInstance; datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const content = document.querySelector('.mat-datepicker-content')!; const cells = content.querySelectorAll('.mat-calendar-body-cell'); @@ -89,6 +97,8 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + tick(); + flush(); expect(datepicker.opened).toBe(true); expect(input.nativeElement.value).toBeFalsy(); @@ -98,14 +108,15 @@ describe('MatDatepickerActions', () => { (content.querySelector('.cancel') as HTMLElement).click(); fixture.detectChanges(); + flush(); expect(datepicker.opened).toBe(false); expect(input.nativeElement.value).toBeFalsy(); expect(control.value).toBeFalsy(); expect(onDateChange).not.toHaveBeenCalled(); - }); + })); - it('should close while keeping the previous control value when clicking on cancel', () => { + it('should close while keeping the previous control value when clicking on cancel', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); const {control, datepicker, onDateChange} = fixture.componentInstance; @@ -114,6 +125,8 @@ describe('MatDatepickerActions', () => { fixture.detectChanges(); datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const content = document.querySelector('.mat-datepicker-content')!; const cells = content.querySelectorAll('.mat-calendar-body-cell'); @@ -124,6 +137,8 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + tick(); + flush(); expect(datepicker.opened).toBe(true); expect(control.value).toBe(value); @@ -131,18 +146,21 @@ describe('MatDatepickerActions', () => { (content.querySelector('.cancel') as HTMLElement).click(); fixture.detectChanges(); + flush(); expect(datepicker.opened).toBe(false); expect(control.value).toBe(value); expect(onDateChange).not.toHaveBeenCalled(); - }); + })); - it('should close and accept the value when clicking on the apply button', () => { + it('should close and accept the value when clicking on the apply button', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); const {control, datepicker, onDateChange, input} = fixture.componentInstance; datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const content = document.querySelector('.mat-datepicker-content')!; const cells = content.querySelectorAll('.mat-calendar-body-cell'); @@ -155,6 +173,8 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + tick(); + flush(); expect(datepicker.opened).toBe(true); expect(input.nativeElement.value).toBeFalsy(); @@ -164,19 +184,22 @@ describe('MatDatepickerActions', () => { (content.querySelector('.apply') as HTMLElement).click(); fixture.detectChanges(); + flush(); expect(datepicker.opened).toBe(false); expect(input.nativeElement.value).toBeTruthy(); expect(control.value).toBeTruthy(); expect(onDateChange).toHaveBeenCalledTimes(1); - }); + })); - it('should revert to the default behavior if the actions are removed', () => { + it('should revert to the default behavior if the actions are removed', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.detectChanges(); const {control, datepicker, onDateChange} = fixture.componentInstance; datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let content = document.querySelector('.mat-datepicker-content')!; let actions = content.querySelector('.mat-datepicker-actions')!; @@ -189,6 +212,8 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + tick(); + flush(); expect(datepicker.opened).toBe(true); expect(control.value).toBeFalsy(); @@ -196,6 +221,7 @@ describe('MatDatepickerActions', () => { (actions.querySelector('.cancel') as HTMLElement).click(); fixture.detectChanges(); + flush(); expect(datepicker.opened).toBe(false); expect(control.value).toBeFalsy(); @@ -217,13 +243,15 @@ describe('MatDatepickerActions', () => { cells[10].click(); fixture.detectChanges(); + tick(); + flush(); expect(datepicker.opened).toBe(false); expect(control.value).toBeTruthy(); expect(onDateChange).toHaveBeenCalledTimes(1); - }); + })); - it('should be able to toggle the actions while the datepicker is open', () => { + it('should be able to toggle the actions while the datepicker is open', fakeAsync(() => { const fixture = createComponent(DatepickerWithActions); fixture.componentInstance.renderActions = false; fixture.changeDetectorRef.markForCheck(); @@ -231,6 +259,8 @@ describe('MatDatepickerActions', () => { fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const content = document.querySelector('.mat-datepicker-content')!; expect(content.querySelector('.mat-datepicker-actions')).toBeFalsy(); @@ -244,7 +274,7 @@ describe('MatDatepickerActions', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(content.querySelector('.mat-datepicker-actions')).toBeFalsy(); - }); + })); }); @Component({ @@ -268,7 +298,6 @@ describe('MatDatepickerActions', () => { `, imports: [MatDatepickerModule, MatInputModule, MatButtonModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithActions { @ViewChild(MatDatepicker) datepicker!: MatDatepicker; diff --git a/src/material/datepicker/datepicker-actions.ts b/src/material/datepicker/datepicker-actions.ts index d7692b5989c2..ac453675405c 100644 --- a/src/material/datepicker/datepicker-actions.ts +++ b/src/material/datepicker/datepicker-actions.ts @@ -8,6 +8,7 @@ import { AfterViewInit, + ChangeDetectionStrategy, Component, Directive, OnDestroy, @@ -29,6 +30,10 @@ export class MatDatepickerApply { private _datepicker = inject, unknown>>(MatDatepickerBase); + constructor(...args: unknown[]); + + constructor() {} + _applySelection() { this._datepicker._applyPendingSelection(); this._datepicker.close(); @@ -42,6 +47,9 @@ export class MatDatepickerApply { }) export class MatDatepickerCancel { _datepicker = inject, unknown>>(MatDatepickerBase); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -58,6 +66,7 @@ export class MatDatepickerCancel {
    `, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MatDatepickerActions implements AfterViewInit, OnDestroy { @@ -68,6 +77,9 @@ export class MatDatepickerActions implements AfterViewInit, OnDestroy { @ViewChild(TemplateRef) _template!: TemplateRef; private _portal!: TemplatePortal; + constructor(...args: unknown[]); + constructor() {} + ngAfterViewInit() { this._portal = new TemplatePortal(this._template, this._viewContainerRef); this._datepicker.registerActions(this._portal); diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 9b4958b3560b..6bed3c4b406b 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -38,6 +38,7 @@ import { afterNextRender, AfterViewInit, booleanAttribute, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, @@ -116,6 +117,7 @@ export type DatepickerDropdownPositionY = 'above' | 'below'; }, exportAs: 'matDatepickerContent', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkTrapFocus, MatCalendar, CdkPortalOutlet, MatButton], }) export class MatDatepickerContent> @@ -185,6 +187,8 @@ export class MatDatepickerContent> /** Id of the label for the `role="dialog"` element. */ _dialogLabelId: string | null = null; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); this._closeButtonText = inject(MatDatepickerIntl).closeCalendarLabel; @@ -547,6 +551,8 @@ export abstract class MatDatepickerBase< private readonly _changeDetectorRef = inject(ChangeDetectorRef); + constructor(...args: unknown[]); + constructor() { if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); @@ -557,7 +563,7 @@ export abstract class MatDatepickerBase< }); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const positionChange = changes['xPosition'] || changes['yPosition']; if (positionChange && !positionChange.firstChange && this._overlayRef) { diff --git a/src/material/datepicker/datepicker-content.scss b/src/material/datepicker/datepicker-content.scss index c22c5d6e0152..9af13a593af4 100644 --- a/src/material/datepicker/datepicker-content.scss +++ b/src/material/datepicker/datepicker-content.scss @@ -119,10 +119,6 @@ $touch-max-height: 788px; // Prevents the content from jumping around on Windows while the animation is running. overflow: visible; - // Prevent the content from shrinking to a point where it doesn't fit all the dates anymore. - // stylelint-disable material/no-prefixes - min-height: fit-content; - &.mat-datepicker-content-animations-enabled { animation: _mat-datepicker-content-dialog-enter 150ms cubic-bezier(0, 0, 0.2, 1); } diff --git a/src/material/datepicker/datepicker-input-base.ts b/src/material/datepicker/datepicker-input-base.ts index 70b879464e2e..52068fa0820c 100644 --- a/src/material/datepicker/datepicker-input-base.ts +++ b/src/material/datepicker/datepicker-input-base.ts @@ -245,6 +245,8 @@ export abstract class MatDatepickerInputBase) { + ngOnChanges(changes: SimpleChanges) { if (dateInputsHaveChanged(changes, this._dateAdapter)) { this.stateChanges.next(undefined); } diff --git a/src/material/datepicker/datepicker-input.ts b/src/material/datepicker/datepicker-input.ts index 65a69d434128..644e7293e377 100644 --- a/src/material/datepicker/datepicker-input.ts +++ b/src/material/datepicker/datepicker-input.ts @@ -132,6 +132,8 @@ export class MatDatepickerInput /** The combined form control validator for this input. */ protected _validator: ValidatorFn | null = null; + constructor(...args: unknown[]); + constructor() { super(); this._validator = Validators.compose(super._getValidators()); diff --git a/src/material/datepicker/datepicker-intl.ts b/src/material/datepicker/datepicker-intl.ts index d07fcab65801..3faeafff9838 100644 --- a/src/material/datepicker/datepicker-intl.ts +++ b/src/material/datepicker/datepicker-intl.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {Subject} from 'rxjs'; /** Datepicker data that requires internationalization. */ -@Service() +@Injectable({providedIn: 'root'}) export class MatDatepickerIntl { /** * Stream that emits whenever the labels here are changed. Use this to notify diff --git a/src/material/datepicker/datepicker-toggle.ts b/src/material/datepicker/datepicker-toggle.ts index 7646acddb317..0c0ff2f61bac 100644 --- a/src/material/datepicker/datepicker-toggle.ts +++ b/src/material/datepicker/datepicker-toggle.ts @@ -8,6 +8,7 @@ import { AfterContentInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, @@ -52,6 +53,7 @@ export class MatDatepickerToggleIcon {} }, exportAs: 'matDatepickerToggle', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconButton], }) export class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDestroy { @@ -91,13 +93,15 @@ export class MatDatepickerToggle implements AfterContentInit, OnChanges, OnDe /** Underlying button element. */ @ViewChild('button') _button!: MatButton; + constructor(...args: unknown[]); + constructor() { const defaultTabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); const parsedTabIndex = Number(defaultTabIndex); this.tabIndex = parsedTabIndex || parsedTabIndex === 0 ? parsedTabIndex : null; } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { if (changes['datepicker']) { this._watchStateChanges(); } diff --git a/src/material/datepicker/datepicker.spec.ts b/src/material/datepicker/datepicker.spec.ts index a2699bb300ad..0392c35af15c 100644 --- a/src/material/datepicker/datepicker.spec.ts +++ b/src/material/datepicker/datepicker.spec.ts @@ -30,9 +30,8 @@ import { ViewChild, ViewEncapsulation, signal, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush, tick} from '@angular/core/testing'; import { FormControl, FormsModule, @@ -73,17 +72,13 @@ describe('MatDatepicker', () => { return TestBed.createComponent(component); } - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - describe('with native adapter', () => { describe('standard datepicker', () => { let fixture: ComponentFixture; let testComponent: StandardDatepicker; let model: MatDateSelectionModel; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(StandardDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); @@ -91,12 +86,13 @@ describe('MatDatepicker', () => { model = fixture.debugElement .query(By.directive(MatDatepicker)) .injector.get(MatDateSelectionModel); - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); it('should initialize with correct value shown in input', () => { if (SUPPORTS_INTL) { @@ -104,16 +100,18 @@ describe('MatDatepicker', () => { } }); - it('open non-touch should open popup', () => { + it('open non-touch should open popup', fakeAsync(() => { expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.cdk-overlay-pane.mat-datepicker-popup')).not.toBeNull(); - }); + })); - it('touch should open dialog', () => { + it('touch should open dialog', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -122,11 +120,13 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); - }); + })); - it('should not be able to open more than one dialog', () => { + it('should not be able to open more than one dialog', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -135,31 +135,39 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(500); + fixture.detectChanges(); dispatchKeyboardEvent(document.querySelector('.mat-calendar-body')!, 'keydown', ENTER); fixture.detectChanges(); + tick(100); testComponent.datepicker.open(); + tick(500); fixture.detectChanges(); + flush(); expect(document.querySelectorAll('.mat-datepicker-dialog').length).toBe(1); - }); + })); - it('should open datepicker if opened input is set to true', () => { + it('should open datepicker if opened input is set to true', fakeAsync(() => { testComponent.opened = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.mat-datepicker-content')).not.toBeNull(); testComponent.opened = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(document.querySelector('.mat-datepicker-content')).toBeNull(); - }); + })); - it('open in disabled mode should not open the calendar', () => { + it('open in disabled mode should not open the calendar', fakeAsync(() => { testComponent.disabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -168,13 +176,15 @@ describe('MatDatepicker', () => { expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); testComponent.datepicker.open(); + tick(); fixture.detectChanges(); + flush(); expect(document.querySelector('.cdk-overlay-pane')).toBeNull(); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); - }); + })); - it('disabled datepicker input should open the calendar if datepicker is enabled', () => { + it('disabled datepicker input should open the calendar if datepicker is enabled', fakeAsync(() => { testComponent.datepicker.disabled = false; testComponent.datepickerInput.disabled = true; fixture.changeDetectorRef.markForCheck(); @@ -184,13 +194,17 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.cdk-overlay-pane')).not.toBeNull(); - }); + })); - it('close should close popup', () => { + it('close should close popup', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const popup = document.querySelector('.cdk-overlay-pane')!; expect(popup).not.toBeNull(); @@ -198,13 +212,16 @@ describe('MatDatepicker', () => { testComponent.datepicker.close(); fixture.detectChanges(); + flush(); expect(popup.getBoundingClientRect().height).toBe(0); - }); + })); - it('should close the popup when pressing ESCAPE', () => { + it('should close the popup when pressing ESCAPE', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.datepicker.opened) .withContext('Expected datepicker to be open.') @@ -212,16 +229,18 @@ describe('MatDatepicker', () => { const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened) .withContext('Expected datepicker to be closed.') .toBe(false); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not close the popup when pressing ESCAPE with a modifier key', () => { + it('should not close the popup when pressing ESCAPE with a modifier key', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(testComponent.datepicker.opened) .withContext('Expected datepicker to be open.') @@ -231,23 +250,26 @@ describe('MatDatepicker', () => { alt: true, }); fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened) .withContext('Expected datepicker to stay open.') .toBe(true); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should set the proper role on the popup', () => { + it('should set the proper role on the popup', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const popup = document.querySelector('.mat-datepicker-content-container')!; expect(popup).toBeTruthy(); expect(popup.getAttribute('role')).toBe('dialog'); - }); + })); - it('should set aria-labelledby to the one from the input, if not placed inside a mat-form-field', () => { + it('should set aria-labelledby to the one from the input, if not placed inside a mat-form-field', fakeAsync(() => { expect(fixture.nativeElement.querySelector('mat-form-field')).toBeFalsy(); const input: HTMLInputElement = fixture.nativeElement.querySelector('input'); @@ -255,37 +277,43 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const popup = document.querySelector( '.cdk-overlay-pane .mat-datepicker-content-container', )!; expect(popup).toBeTruthy(); expect(popup.getAttribute('aria-labelledby')).toBe('test-label'); - }); + })); - it('close should close dialog', () => { + it('close should close dialog', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); testComponent.datepicker.close(); fixture.detectChanges(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); - }); + })); - it('setting selected via click should update input and close calendar', () => { + it('setting selected via click should update input and close calendar', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); @@ -293,18 +321,21 @@ describe('MatDatepicker', () => { let cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[1], 'click'); fixture.detectChanges(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); - }); + })); - it('setting selected via enter press should update input and close calendar', () => { + it('setting selected via enter press should update input and close calendar', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); @@ -313,19 +344,22 @@ describe('MatDatepicker', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW); fixture.detectChanges(); + tick(); + flush(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); dispatchKeyboardEvent(calendarBodyEl, 'keyup', ENTER); fixture.detectChanges(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); - }); + })); it( 'clicking the currently selected date should close the calendar ' + 'without firing selectedChanged', - () => { + fakeAsync(() => { const spy = jasmine.createSpy('selectionChanged spy'); const selectedSubscription = model.selectionChanged.subscribe(spy); @@ -333,6 +367,7 @@ describe('MatDatepicker', () => { const currentDay = changeCount; testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(document.querySelector('mat-datepicker-content')).not.toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, currentDay)); @@ -340,21 +375,24 @@ describe('MatDatepicker', () => { let cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[1], 'click'); fixture.detectChanges(); + flush(); } expect(spy).toHaveBeenCalledTimes(1); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2)); selectedSubscription.unsubscribe(); - }, + }), ); - it('pressing enter on the currently selected date should close the calendar without firing selectedChanged', () => { + it('pressing enter on the currently selected date should close the calendar without firing selectedChanged', fakeAsync(() => { const spy = jasmine.createSpy('selectionChanged spy'); const selectedSubscription = model.selectionChanged.subscribe(spy); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let calendarBodyEl = document.querySelector('.mat-calendar-body') as HTMLElement; expect(calendarBodyEl).not.toBeNull(); @@ -362,12 +400,13 @@ describe('MatDatepicker', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); + flush(); expect(spy).not.toHaveBeenCalled(); expect(document.querySelector('.mat-datepicker-dialog')).toBeNull(); expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1)); selectedSubscription.unsubscribe(); - }); + })); it('startAt should fallback to input value', () => { expect(testComponent.datepicker.startAt).toEqual(new Date(2020, JAN, 1)); @@ -380,12 +419,14 @@ describe('MatDatepicker', () => { .toBe('input'); }); - it('input should aria-owns calendar after opened in non-touch mode', () => { + it('input should aria-owns calendar after opened in non-touch mode', fakeAsync(() => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; expect(inputEl.getAttribute('aria-owns')).toBeNull(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let ownedElementId = inputEl.getAttribute('aria-owns'); expect(ownedElementId).not.toBeNull(); @@ -393,9 +434,9 @@ describe('MatDatepicker', () => { let ownedElement = document.getElementById(ownedElementId); expect(ownedElement).not.toBeNull(); expect((ownedElement as Element).tagName.toLowerCase()).toBe('mat-calendar'); - }); + })); - it('input should aria-owns calendar after opened in touch mode', () => { + it('input should aria-owns calendar after opened in touch mode', fakeAsync(() => { testComponent.touch = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -405,6 +446,8 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let ownedElementId = inputEl.getAttribute('aria-owns'); expect(ownedElementId).not.toBeNull(); @@ -412,7 +455,7 @@ describe('MatDatepicker', () => { let ownedElement = document.getElementById(ownedElementId); expect(ownedElement).not.toBeNull(); expect((ownedElement as Element).tagName.toLowerCase()).toBe('mat-calendar'); - }); + })); it('should not throw when given wrong data type', () => { testComponent.date = '1/1/2017' as any; @@ -421,7 +464,7 @@ describe('MatDatepicker', () => { expect(() => fixture.detectChanges()).not.toThrow(); }); - it('should clear out the backdrop subscriptions on close', () => { + it('should clear out the backdrop subscriptions on close', fakeAsync(() => { for (let i = 0; i < 3; i++) { testComponent.datepicker.open(); fixture.detectChanges(); @@ -443,9 +486,9 @@ describe('MatDatepicker', () => { expect(spy).toHaveBeenCalledTimes(1); expect(testComponent.datepicker.opened).toBe(false); subscription.unsubscribe(); - }); + })); - it('should reset the datepicker when it is closed externally', async () => { + it('should reset the datepicker when it is closed externally', fakeAsync(() => { TestBed.resetTestingModule(); const scrolledSubject = new Subject(); @@ -468,20 +511,22 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(testComponent.datepicker.opened).toBe(true); scrolledSubject.next(); - // The scroll strategy has some debouncing we need to wait for. - await wait(50); + flush(); fixture.detectChanges(); expect(testComponent.datepicker.opened).toBe(false); - }); + })); - it('should close the datepicker using ALT + UP_ARROW', () => { + it('should close the datepicker using ALT + UP_ARROW', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.datepicker.opened).toBe(true); @@ -489,13 +534,16 @@ describe('MatDatepicker', () => { dispatchEvent(document.body, event); fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened).toBe(false); - }); + })); - it('should not close the datepicker when using CTRL + SHIFT + ALT + UP_ARROW', () => { + it('should not close the datepicker when using CTRL + SHIFT + ALT + UP_ARROW', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.datepicker.opened).toBe(true); @@ -507,23 +555,26 @@ describe('MatDatepicker', () => { dispatchEvent(document.body, event); fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened).toBe(true); - }); + })); - it('should open the datepicker using ALT + DOWN_ARROW', () => { + it('should open the datepicker using ALT + DOWN_ARROW', fakeAsync(() => { expect(testComponent.datepicker.opened).toBe(false); const event = createKeyboardEvent('keydown', DOWN_ARROW, undefined, {alt: true}); dispatchEvent(fixture.nativeElement.querySelector('input'), event); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.datepicker.opened).toBe(true); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not open for ALT + DOWN_ARROW on readonly input', () => { + it('should not open for ALT + DOWN_ARROW on readonly input', fakeAsync(() => { const input = fixture.nativeElement.querySelector('input'); expect(testComponent.datepicker.opened).toBe(false); @@ -534,12 +585,13 @@ describe('MatDatepicker', () => { dispatchEvent(input, event); fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened).toBe(false); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should not open the datepicker using SHIFT + CTRL + ALT + DOWN_ARROW', () => { + it('should not open the datepicker using SHIFT + CTRL + ALT + DOWN_ARROW', fakeAsync(() => { expect(testComponent.datepicker.opened).toBe(false); const event = createKeyboardEvent('keydown', DOWN_ARROW, undefined, { @@ -550,15 +602,19 @@ describe('MatDatepicker', () => { dispatchEvent(fixture.nativeElement.querySelector('input'), event); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.datepicker.opened).toBe(false); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should show the invisible close button on focus', () => { + it('should show the invisible close button on focus', fakeAsync(() => { testComponent.opened = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + tick(); + flush(); const button = document.querySelector('.mat-datepicker-close-button') as HTMLButtonElement; expect(button.classList).toContain('cdk-visually-hidden'); @@ -570,23 +626,26 @@ describe('MatDatepicker', () => { dispatchFakeEvent(button, 'blur'); fixture.detectChanges(); expect(button.classList).toContain('cdk-visually-hidden'); - }); + })); - it('should close the overlay when clicking on the invisible close button', () => { + it('should close the overlay when clicking on the invisible close button', fakeAsync(() => { testComponent.opened = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + tick(); + flush(); const button = document.querySelector('.mat-datepicker-close-button') as HTMLButtonElement; expect(document.querySelector('.mat-datepicker-content')).not.toBeNull(); button.click(); fixture.detectChanges(); + flush(); expect(document.querySelector('.mat-datepicker-content')).toBeNull(); - }); + })); - it('should prevent the default action of navigation keys before the focus timeout has elapsed', () => { + it('should prevent the default action of navigation keys before the focus timeout has elapsed', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); @@ -599,18 +658,21 @@ describe('MatDatepicker', () => { .withContext(`Expected default action to be prevented for key code ${keyCode}`) .toBe(true); }); - }); + + tick(); + flush(); + })); }); describe('datepicker with too many inputs', () => { - it('should throw when multiple inputs registered', () => { + it('should throw when multiple inputs registered', fakeAsync(() => { const fixture = createComponent(MultiInputDatepicker, [provideNativeDateAdapter()]); expect(() => fixture.detectChanges()).toThrow(); - }); + })); }); describe('datepicker that is assigned to input at a later point', () => { - it('should not throw on ALT + DOWN_ARROW for input without datepicker', () => { + it('should not throw on ALT + DOWN_ARROW for input without datepicker', fakeAsync(() => { const fixture = createComponent(DelayedDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); @@ -618,10 +680,11 @@ describe('MatDatepicker', () => { const event = createKeyboardEvent('keydown', DOWN_ARROW, undefined, {alt: true}); dispatchEvent(fixture.nativeElement.querySelector('input'), event); fixture.detectChanges(); + flush(); }).not.toThrow(); - }); + })); - it('should handle value changes when a datepicker is assigned after init', () => { + it('should handle value changes when a datepicker is assigned after init', fakeAsync(() => { const fixture = createComponent(DelayedDatepicker, [provideNativeDateAdapter()]); const testComponent: DelayedDatepicker = fixture.componentInstance; const toSelect = new Date(2017, JAN, 1); @@ -640,52 +703,54 @@ describe('MatDatepicker', () => { testComponent.assignedDatepicker.select(toSelect); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(testComponent.datepickerInput.value).toEqual(toSelect); expect(model.selection).toEqual(toSelect); - }); + })); }); describe('datepicker with no inputs', () => { let fixture: ComponentFixture; let testComponent: NoInputDatepicker; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(NoInputDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); it('should not throw when accessing disabled property', () => { expect(() => testComponent.datepicker.disabled).not.toThrow(); }); - it('should throw when opened with no registered inputs', () => { + it('should throw when opened with no registered inputs', fakeAsync(() => { expect(() => testComponent.datepicker.open()).toThrow(); - }); + })); }); describe('datepicker with startAt', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithStartAt; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithStartAt, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); it('explicit startAt should override input value', () => { expect(testComponent.datepicker.startAt).toEqual(new Date(2010, JAN, 1)); @@ -696,21 +761,23 @@ describe('MatDatepicker', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithStartViewYear; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithStartViewYear, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); - it('should start at the specified view', () => { + it('should start at the specified view', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const firstCalendarCell = document.querySelector('.mat-calendar-body-cell')!; @@ -721,45 +788,51 @@ describe('MatDatepicker', () => { expect(firstCalendarCell.textContent!.trim()) .withContext('Expected the calendar to be in year-view') .toBe('JAN'); - }); + })); - it('should fire yearSelected when user selects calendar year in year view', () => { + it('should fire yearSelected when user selects calendar year in year view', fakeAsync(() => { spyOn(testComponent, 'onYearSelection'); expect(testComponent.onYearSelection).not.toHaveBeenCalled(); testComponent.datepicker.open(); + tick(); fixture.detectChanges(); + flush(); const cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[0], 'click'); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.onYearSelection).toHaveBeenCalled(); - }); + })); }); describe('datepicker with startView set to multiyear', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithStartViewMultiYear; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithStartViewMultiYear, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; spyOn(testComponent, 'onMultiYearSelection'); - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); - it('should start at the specified view', () => { + it('should start at the specified view', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const firstCalendarCell = document.querySelector('.mat-calendar-body-cell')!; @@ -770,21 +843,24 @@ describe('MatDatepicker', () => { expect(firstCalendarCell.textContent!.trim()) .withContext('Expected the calendar to be in multi-year-view') .toBe('2016'); - }); + })); - it('should fire yearSelected when user selects calendar year in multiyear view', () => { + it('should fire yearSelected when user selects calendar year in multiyear view', fakeAsync(() => { expect(testComponent.onMultiYearSelection).not.toHaveBeenCalled(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[0], 'click'); fixture.detectChanges(); + tick(); + flush(); expect(testComponent.onMultiYearSelection).toHaveBeenCalled(); - }); + })); }); describe('datepicker with ngModel', () => { @@ -792,21 +868,21 @@ describe('MatDatepicker', () => { let testComponent: DatepickerWithNgModel; let model: MatDateSelectionModel; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithNgModel, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; model = fixture.debugElement .query(By.directive(MatDatepicker)) .injector.get(MatDateSelectionModel); - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); - it('should update datepicker when model changes', async () => { + it('should update datepicker when model changes', fakeAsync(() => { expect(testComponent.datepickerInput.value).toBeNull(); expect(model.selection).toBeNull(); @@ -814,24 +890,26 @@ describe('MatDatepicker', () => { testComponent.selected = selected; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(testComponent.datepickerInput.value).toEqual(selected); expect(model.selection).toEqual(selected); - }); + })); - it('should update model when date is selected', () => { + it('should update model when date is selected', fakeAsync(() => { expect(testComponent.selected).toBeNull(); expect(testComponent.datepickerInput.value).toBeNull(); let selected = new Date(2017, JAN, 1); testComponent.datepicker.select(selected); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(testComponent.selected).toEqual(selected); expect(testComponent.datepickerInput.value).toEqual(selected); - }); + })); it('should mark input dirty after input event', () => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; @@ -845,16 +923,18 @@ describe('MatDatepicker', () => { expect(inputEl.classList).toContain('ng-dirty'); }); - it('should mark input dirty after date selected', () => { + it('should mark input dirty after date selected', fakeAsync(() => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; expect(inputEl.classList).toContain('ng-pristine'); testComponent.datepicker.select(new Date(2017, JAN, 1)); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(inputEl.classList).toContain('ng-dirty'); - }); + })); it('should mark input dirty after invalid value is typed in', () => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; @@ -868,7 +948,7 @@ describe('MatDatepicker', () => { expect(inputEl.classList).toContain('ng-dirty'); }); - it('should not mark dirty after model change', () => { + it('should not mark dirty after model change', fakeAsync(() => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; expect(inputEl.classList).toContain('ng-pristine'); @@ -876,9 +956,11 @@ describe('MatDatepicker', () => { testComponent.selected = new Date(2017, JAN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(inputEl.classList).toContain('ng-pristine'); - }); + })); it('should mark input touched on blur', () => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; @@ -896,21 +978,26 @@ describe('MatDatepicker', () => { expect(inputEl.classList).toContain('ng-touched'); }); - it('should mark input as touched when the datepicker is closed', () => { + it('should mark input as touched when the datepicker is closed', fakeAsync(() => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; expect(inputEl.classList).toContain('ng-untouched'); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); + fixture.detectChanges(); expect(inputEl.classList).toContain('ng-untouched'); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(inputEl.classList).toContain('ng-touched'); - }); + })); it('should reformat the input value on blur', () => { if (SUPPORTS_INTL) { @@ -944,16 +1031,18 @@ describe('MatDatepicker', () => { expect(inputEl.value).toBe('very-valid-date'); }); - it('should mark input touched on calendar selection', () => { + it('should mark input touched on calendar selection', fakeAsync(() => { let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; expect(inputEl.classList).toContain('ng-untouched'); testComponent.datepicker.select(new Date(2017, JAN, 1)); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(inputEl.classList).toContain('ng-touched'); - }); + })); }); describe('datepicker with formControl', () => { @@ -961,7 +1050,7 @@ describe('MatDatepicker', () => { let testComponent: DatepickerWithFormControl; let model: MatDateSelectionModel; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithFormControl, [provideNativeDateAdapter()]); fixture.detectChanges(); @@ -969,12 +1058,12 @@ describe('MatDatepicker', () => { model = fixture.debugElement .query(By.directive(MatDatepicker)) .injector.get(MatDateSelectionModel); - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); it('should update datepicker when formControl changes', () => { expect(testComponent.datepickerInput.value).toBeNull(); @@ -1023,7 +1112,7 @@ describe('MatDatepicker', () => { it( 'should not dispatch FormControl change event for invalid values on input when set ' + 'to update on blur', - () => { + fakeAsync(() => { const formControl = new FormControl({value: null} as unknown as Date, {updateOn: 'blur'}); const spy = jasmine.createSpy('change spy'); const subscription = formControl.valueChanges.subscribe(spy); @@ -1032,6 +1121,7 @@ describe('MatDatepicker', () => { inputEl.value = value; dispatchFakeEvent(inputEl, 'input'); fixture.detectChanges(); + flush(); fixture.detectChanges(); }; @@ -1052,11 +1142,12 @@ describe('MatDatepicker', () => { dispatchFakeEvent(inputEl, 'blur'); fixture.detectChanges(); + flush(); fixture.detectChanges(); expect(spy).toHaveBeenCalledTimes(1); subscription.unsubscribe(); - }, + }), ); it('should set the matDatepickerParse error when an invalid value is typed for the first time', () => { @@ -1106,17 +1197,18 @@ describe('MatDatepicker', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithToggle; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithToggle, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); it('should set `aria-haspopup` on the toggle button', () => { const button = fixture.debugElement.query(By.css('button'))!; @@ -1194,7 +1286,7 @@ describe('MatDatepicker', () => { expect(icon.getAttribute('focusable')).toBe('false'); }); - it('should restore focus to the toggle after the calendar is closed', async () => { + it('should restore focus to the toggle after the calendar is closed', fakeAsync(() => { let toggle = fixture.debugElement.query(By.css('button'))!.nativeElement; fixture.componentInstance.touchUI = false; @@ -1206,22 +1298,25 @@ describe('MatDatepicker', () => { fixture.componentInstance.datepicker.open(); fixture.detectChanges(); - await wait(50); // Wait for focus to move to calendar - fixture.detectChanges(); + tick(); let pane = document.querySelector('.cdk-overlay-pane')!; + + expect(pane).withContext('Expected calendar to be open.').toBeTruthy(); expect(pane.contains(document.activeElement)) .withContext('Expected focus to be inside the calendar.') .toBe(true); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); - await wait(200); + flush(); - expect(document.activeElement).toBe(toggle, 'Expected focus to be restored to the toggle.'); - }); + expect(document.activeElement) + .withContext('Expected focus to be restored to toggle.') + .toBe(toggle); + })); - it('should restore focus when placed inside a shadow root', () => { + it('should restore focus when placed inside a shadow root', fakeAsync(() => { if (!_supportsShadowDom()) { return; } @@ -1241,15 +1336,17 @@ describe('MatDatepicker', () => { spyOn(toggle, 'focus').and.callThrough(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); + flush(); // We have to assert by looking at the `focus` method, because // `document.activeElement` will return the shadow root. expect(toggle.focus).toHaveBeenCalled(); - }); + })); - it('should allow for focus restoration to be disabled', async () => { + it('should allow for focus restoration to be disabled', fakeAsync(() => { let toggle = fixture.debugElement.query(By.css('button'))!.nativeElement; fixture.componentInstance.touchUI = false; @@ -1262,25 +1359,25 @@ describe('MatDatepicker', () => { fixture.componentInstance.datepicker.open(); fixture.detectChanges(); - await wait(50); // Wait for focus to move to calendar - fixture.detectChanges(); + tick(); let pane = document.querySelector('.cdk-overlay-pane')!; + + expect(pane).withContext('Expected calendar to be open.').toBeTruthy(); expect(pane.contains(document.activeElement)) .withContext('Expected focus to be inside the calendar.') .toBe(true); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); - await wait(200); + flush(); - expect(document.activeElement).not.toBe( - toggle, - 'Expected focus not to be restored to the toggle.', - ); - }); + expect(document.activeElement) + .not.withContext('Expected focus not to be restored to toggle.') + .toBe(toggle); + })); - it('should not override focus if it was moved inside the closed event in touchUI mode', async () => { + it('should not override focus if it was moved inside the closed event in touchUI mode', fakeAsync(() => { const focusTarget = document.createElement('button'); const datepicker = fixture.componentInstance.datepicker; const subscription = datepicker.closedStream.subscribe(() => focusTarget.focus()); @@ -1303,7 +1400,7 @@ describe('MatDatepicker', () => { datepicker.open(); fixture.detectChanges(); - await wait(500); + tick(500); fixture.detectChanges(); expect(document.activeElement).not.toBe( @@ -1313,7 +1410,7 @@ describe('MatDatepicker', () => { datepicker.close(); fixture.detectChanges(); - await wait(500); + tick(500); fixture.detectChanges(); expect(document.activeElement) @@ -1322,7 +1419,7 @@ describe('MatDatepicker', () => { focusTarget.remove(); subscription.unsubscribe(); - }); + })); it('should re-render when the i18n labels change', () => { const intl = TestBed.inject(MatDatepickerIntl); @@ -1335,41 +1432,49 @@ describe('MatDatepicker', () => { expect(toggle.getAttribute('aria-label')).toBe('Open the calendar, perhaps?'); }); - it('should toggle the active state of the datepicker toggle', () => { + it('should toggle the active state of the datepicker toggle', fakeAsync(() => { const toggle = fixture.debugElement.query(By.css('mat-datepicker-toggle'))!.nativeElement; expect(toggle.classList).not.toContain('mat-datepicker-toggle-active'); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(toggle.classList).toContain('mat-datepicker-toggle-active'); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(toggle.classList).not.toContain('mat-datepicker-toggle-active'); - }); + })); - it('should set aria-expanded on the toggle', () => { + it('should set aria-expanded on the toggle', fakeAsync(() => { const button = fixture.nativeElement.querySelector('mat-datepicker-toggle button'); expect(button.getAttribute('aria-expanded')).toBe('false'); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(button.getAttribute('aria-expanded')).toBe('true'); fixture.componentInstance.datepicker.close(); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(button.getAttribute('aria-expanded')).toBe('false'); - }); + })); }); describe('datepicker with custom mat-datepicker-toggle icon', () => { - it('should be able to override the mat-datepicker-toggle icon', () => { + it('should be able to override the mat-datepicker-toggle icon', fakeAsync(() => { const fixture = createComponent(DatepickerWithCustomIcon, [provideNativeDateAdapter()]); fixture.detectChanges(); @@ -1380,7 +1485,7 @@ describe('MatDatepicker', () => { expect(fixture.nativeElement.querySelector('.mat-datepicker-toggle mat-icon')) .withContext('Expected default icon to be removed.') .toBeFalsy(); - }); + })); }); describe('datepicker with tabindex on mat-datepicker-toggle', () => { @@ -1411,16 +1516,17 @@ describe('MatDatepicker', () => { let fixture: ComponentFixture; let testComponent: FormFieldDatepicker; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(FormFieldDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); it('should float the placeholder when an invalid value is entered', () => { testComponent.datepickerInput.value = 'totally-not-a-date' as any; @@ -1433,11 +1539,13 @@ describe('MatDatepicker', () => { ); }); - it('should pass the form field theme color to the overlay', () => { + it('should pass the form field theme color to the overlay', fakeAsync(() => { testComponent.formField.color = 'primary'; fixture.changeDetectorRef.markForCheck(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let contentEl = document.querySelector('.mat-datepicker-content')!; @@ -1445,6 +1553,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.close(); fixture.detectChanges(); + flush(); testComponent.formField.color = 'warn'; fixture.changeDetectorRef.markForCheck(); @@ -1452,25 +1561,29 @@ describe('MatDatepicker', () => { contentEl = document.querySelector('.mat-datepicker-content')!; fixture.detectChanges(); + tick(); + flush(); expect(contentEl.classList).toContain('mat-warn'); expect(contentEl.classList).not.toContain('mat-primary'); - }); + })); - it('should prefer the datepicker color over the form field one', () => { + it('should prefer the datepicker color over the form field one', fakeAsync(() => { testComponent.datepicker.color = 'accent'; testComponent.formField.color = 'warn'; fixture.changeDetectorRef.markForCheck(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const contentEl = document.querySelector('.mat-datepicker-content')!; expect(contentEl.classList).toContain('mat-accent'); expect(contentEl.classList).not.toContain('mat-warn'); - }); + })); - it('should set aria-labelledby of the overlay to the form field label', () => { + it('should set aria-labelledby of the overlay to the form field label', fakeAsync(() => { const label: HTMLElement = fixture.nativeElement.querySelector('label'); expect(label).toBeTruthy(); @@ -1478,124 +1591,128 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); const popup = document.querySelector( '.cdk-overlay-pane .mat-datepicker-content-container', )!; expect(popup).toBeTruthy(); expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id')); - }); + })); }); describe('datepicker with min and max dates and validation', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithMinAndMaxValidation; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithMinAndMaxValidation, [provideNativeDateAdapter()]); fixture.detectChanges(); + flush(); testComponent = fixture.componentInstance; - }); + })); - async function revalidate() { + function revalidate() { fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); } - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); it('should use min and max dates specified by the input', () => { expect(testComponent.datepicker._getMinDate()).toEqual(new Date(2010, JAN, 1)); expect(testComponent.datepicker._getMaxDate()).toEqual(new Date(2020, JAN, 1)); }); - it('should mark invalid when value is before min', async () => { + it('should mark invalid when value is before min', fakeAsync(() => { testComponent.date = new Date(2009, DEC, 31); fixture.changeDetectorRef.markForCheck(); - await revalidate(); + revalidate(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).toContain( 'ng-invalid', ); - }); + })); - it('should mark invalid when value is after max', async () => { + it('should mark invalid when value is after max', fakeAsync(() => { testComponent.date = new Date(2020, JAN, 2); fixture.changeDetectorRef.markForCheck(); - await revalidate(); + revalidate(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).toContain( 'ng-invalid', ); - }); + })); - it('should not mark invalid when value equals min', async () => { + it('should not mark invalid when value equals min', fakeAsync(() => { testComponent.date = testComponent.datepicker._getMinDate(); fixture.changeDetectorRef.markForCheck(); - await revalidate(); + revalidate(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).not.toContain( 'ng-invalid', ); - }); + })); - it('should not mark invalid when value equals max', async () => { + it('should not mark invalid when value equals max', fakeAsync(() => { testComponent.date = testComponent.datepicker._getMaxDate(); fixture.changeDetectorRef.markForCheck(); - await revalidate(); + revalidate(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).not.toContain( 'ng-invalid', ); - }); + })); - it('should not mark invalid when value is between min and max', async () => { + it('should not mark invalid when value is between min and max', fakeAsync(() => { testComponent.date = new Date(2010, JAN, 2); fixture.changeDetectorRef.markForCheck(); - await revalidate(); + revalidate(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).not.toContain( 'ng-invalid', ); - }); + })); - it('should update validity when switching between null and invalid', async () => { + it('should update validity when switching between null and invalid', fakeAsync(() => { const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; inputEl.value = ''; dispatchFakeEvent(inputEl, 'input'); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(true); inputEl.value = 'abcdefg'; dispatchFakeEvent(inputEl, 'input'); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(false); inputEl.value = ''; dispatchFakeEvent(inputEl, 'input'); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(true); - }); + })); - it('should update validity when a value is assigned', async () => { + it('should update validity when a value is assigned', fakeAsync(() => { const inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement; inputEl.value = ''; dispatchFakeEvent(inputEl, 'input'); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(true); inputEl.value = 'abcdefg'; dispatchFakeEvent(inputEl, 'input'); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(false); @@ -1606,13 +1723,13 @@ describe('MatDatepicker', () => { .query(By.directive(MatDatepicker)) .injector.get>(MatDateSelectionModel); model.updateSelection(validDate, null); - await revalidate(); + revalidate(); expect(testComponent.model.valid).toBe(true); expect(testComponent.date).toBe(validDate); - }); + })); - it('should update the calendar when the min/max dates change', () => { + it('should update the calendar when the min/max dates change', fakeAsync(() => { const getDisabledCells = () => { return document.querySelectorAll('.mat-calendar-body-disabled').length; }; @@ -1628,6 +1745,8 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); let disabledCellCount = getDisabledCells(); expect(disabledCellCount).not.toBe(0); @@ -1635,6 +1754,7 @@ describe('MatDatepicker', () => { testComponent.minDate = new Date(2020, JAN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(getDisabledCells()).not.toBe(disabledCellCount); disabledCellCount = getDisabledCells(); @@ -1642,9 +1762,10 @@ describe('MatDatepicker', () => { testComponent.maxDate = new Date(2020, JAN, 10); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); expect(getDisabledCells()).not.toBe(disabledCellCount); - }); + })); }); describe('datepicker with filter and validation', () => { @@ -1658,16 +1779,17 @@ describe('MatDatepicker', () => { testComponent = fixture.componentInstance; }); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); - it('should mark input invalid', async () => { + it('should mark input invalid', fakeAsync(() => { testComponent.date = new Date(2017, JAN, 1); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).toContain( @@ -1677,34 +1799,36 @@ describe('MatDatepicker', () => { testComponent.date = new Date(2017, JAN, 2); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.debugElement.query(By.css('input'))!.nativeElement.classList).not.toContain( 'ng-invalid', ); - }); + })); - it('should disable filtered calendar cells', () => { + it('should disable filtered calendar cells', fakeAsync(() => { fixture.detectChanges(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); let cells = document.querySelectorAll('.mat-calendar-body-cell'); expect(cells[0].classList).toContain('mat-calendar-body-disabled'); expect(cells[1].classList).not.toContain('mat-calendar-body-disabled'); - }); + })); - it('should revalidate when a new function is assigned', async () => { + it('should revalidate when a new function is assigned', fakeAsync(() => { const classList = fixture.debugElement.query(By.css('input'))!.nativeElement.classList; testComponent.date = new Date(2017, JAN, 1); testComponent.filter = () => true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(classList).not.toContain('ng-invalid'); @@ -1712,19 +1836,19 @@ describe('MatDatepicker', () => { testComponent.filter = () => false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(classList).toContain('ng-invalid'); - }); + })); - it('should not dispatch the change event if a new function with the same result is assigned', () => { + it('should not dispatch the change event if a new function with the same result is assigned', fakeAsync(() => { const spy = jasmine.createSpy('change spy'); const subscription = fixture.componentInstance.model.valueChanges?.subscribe(spy); testComponent.filter = () => false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - + flush(); fixture.detectChanges(); expect(spy).toHaveBeenCalledTimes(1); @@ -1732,10 +1856,12 @@ describe('MatDatepicker', () => { testComponent.filter = () => false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); + flush(); + fixture.detectChanges(); expect(spy).toHaveBeenCalledTimes(1); subscription?.unsubscribe(); - }); + })); }); describe('datepicker with change and input events', () => { @@ -1743,7 +1869,7 @@ describe('MatDatepicker', () => { let testComponent: DatepickerWithChangeAndInputEvents; let inputEl: HTMLInputElement; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithChangeAndInputEvents, [provideNativeDateAdapter()]); fixture.detectChanges(); @@ -1754,12 +1880,12 @@ describe('MatDatepicker', () => { spyOn(testComponent, 'onInput'); spyOn(testComponent, 'onDateChange'); spyOn(testComponent, 'onDateInput'); - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); it('should fire input and dateInput events when user types input', () => { expect(testComponent.onChange).not.toHaveBeenCalled(); @@ -1792,7 +1918,7 @@ describe('MatDatepicker', () => { expect(testComponent.onDateInput).not.toHaveBeenCalled(); }); - it('should fire dateChange and dateInput events when user selects calendar date', () => { + it('should fire dateChange and dateInput events when user selects calendar date', fakeAsync(() => { expect(testComponent.onChange).not.toHaveBeenCalled(); expect(testComponent.onDateChange).not.toHaveBeenCalled(); expect(testComponent.onInput).not.toHaveBeenCalled(); @@ -1800,18 +1926,20 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(document.querySelector('.mat-datepicker-dialog')).not.toBeNull(); const cells = document.querySelectorAll('.mat-calendar-body-cell'); dispatchMouseEvent(cells[0], 'click'); fixture.detectChanges(); + flush(); expect(testComponent.onChange).not.toHaveBeenCalled(); expect(testComponent.onDateChange).toHaveBeenCalled(); expect(testComponent.onInput).not.toHaveBeenCalled(); expect(testComponent.onDateInput).toHaveBeenCalled(); - }); + })); it('should not fire the dateInput event if the value has not changed', () => { expect(testComponent.onDateInput).not.toHaveBeenCalled(); @@ -1864,54 +1992,59 @@ describe('MatDatepicker', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithISOStrings; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithISOStrings, [provideNativeDateAdapter()]); + flush(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + })); - it('should coerce ISO strings', async () => { + it('should coerce ISO strings', fakeAsync(() => { expect(() => fixture.detectChanges()).not.toThrow(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(testComponent.datepicker.startAt).toEqual(new Date(2017, JUL, 1)); expect(testComponent.datepickerInput.value).toEqual(new Date(2017, JUN, 1)); expect(testComponent.datepickerInput.min).toEqual(new Date(2017, JAN, 1)); expect(testComponent.datepickerInput.max).toEqual(new Date(2017, DEC, 31)); - }); + })); }); describe('with events', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithEvents; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithEvents, [provideNativeDateAdapter()]); fixture.detectChanges(); + flush(); testComponent = fixture.componentInstance; - }); + })); - it('should dispatch an event when a datepicker is opened', () => { + it('should dispatch an event when a datepicker is opened', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); expect(testComponent.openedSpy).toHaveBeenCalled(); - }); + })); - it('should dispatch an event when a datepicker is closed', () => { + it('should dispatch an event when a datepicker is closed', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); testComponent.datepicker.close(); + flush(); fixture.detectChanges(); expect(testComponent.closedSpy).toHaveBeenCalled(); - }); + })); }); describe('datepicker that opens on focus', () => { @@ -1919,30 +2052,31 @@ describe('MatDatepicker', () => { let testComponent: DatepickerOpeningOnFocus; let input: HTMLInputElement; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerOpeningOnFocus, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; input = fixture.debugElement.query(By.css('input'))!.nativeElement; - }); + })); - it('should not reopen if the browser fires the focus event asynchronously', async () => { + it('should not reopen if the browser fires the focus event asynchronously', fakeAsync(() => { // Stub out the real focus method so we can call it reliably. spyOn(input, 'focus').and.callFake(() => { - // Dispatch the event handler to simulate the IE11 behavior. + // Dispatch the event handler async to simulate the IE11 behavior. Promise.resolve().then(() => dispatchFakeEvent(input, 'focus')); }); // Open initially by focusing. input.focus(); fixture.detectChanges(); - await fixture.whenStable(); - fixture.detectChanges(); + tick(); + flush(); // Due to some browser limitations we can't install a stub on `document.activeElement` // so instead we have to override the previously-focused element manually. (fixture.componentInstance.datepicker as any)._focusedElementBeforeOpen = input; + // Ensure that the datepicker is actually open. // Ensure that the datepicker is actually open. expect(testComponent.datepicker.opened) .withContext('Expected datepicker to be open.') @@ -1955,19 +2089,19 @@ describe('MatDatepicker', () => { // Schedule the input to be focused asynchronously. input.focus(); fixture.detectChanges(); + tick(); // Flush out the scheduled tasks. - await wait(200); - fixture.detectChanges(); + flush(); expect(testComponent.datepicker.opened) .withContext('Expected datepicker to be closed.') .toBe(false); - }); + })); }); describe('datepicker directionality', () => { - it('should pass along the directionality to the popup', () => { + it('should pass along the directionality to the popup', fakeAsync(() => { const fixture = createComponent(StandardDatepicker, [ provideNativeDateAdapter(), provideFakeDirectionality('rtl'), @@ -1976,13 +2110,14 @@ describe('MatDatepicker', () => { fixture.detectChanges(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); const overlay = document.querySelector('.cdk-overlay-connected-position-bounding-box')!; expect(overlay.getAttribute('dir')).toBe('rtl'); - }); + })); - it('should update the popup direction if the directionality value changes', () => { + it('should update the popup direction if the directionality value changes', fakeAsync(() => { const dir = signal('ltr'); const fixture = createComponent(StandardDatepicker, [ provideNativeDateAdapter(), @@ -1992,6 +2127,7 @@ describe('MatDatepicker', () => { fixture.detectChanges(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); let overlay = document.querySelector('.cdk-overlay-connected-position-bounding-box')!; @@ -1999,17 +2135,19 @@ describe('MatDatepicker', () => { fixture.componentInstance.datepicker.close(); fixture.detectChanges(); + flush(); dir.set('rtl'); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); overlay = document.querySelector('.cdk-overlay-connected-position-bounding-box')!; expect(overlay.getAttribute('dir')).toBe('rtl'); - }); + })); - it('should pass along the directionality to the dialog in touch mode', () => { + it('should pass along the directionality to the dialog in touch mode', fakeAsync(() => { const fixture = createComponent(StandardDatepicker, [ provideNativeDateAdapter(), provideFakeDirectionality('rtl'), @@ -2020,11 +2158,12 @@ describe('MatDatepicker', () => { fixture.detectChanges(); fixture.componentInstance.datepicker.open(); fixture.detectChanges(); + tick(); const overlay = document.querySelector('.cdk-global-overlay-wrapper')!; expect(overlay.getAttribute('dir')).toBe('rtl'); - }); + })); }); }); @@ -2091,18 +2230,19 @@ describe('MatDatepicker', () => { let testComponent: StandardDatepicker; let input: HTMLElement; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(StandardDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; input = fixture.debugElement.query(By.css('input'))!.nativeElement; input.style.position = 'fixed'; - }); + })); - it('should be below and to the right when there is plenty of space', () => { + it('should be below and to the right when there is plenty of space', fakeAsync(() => { input.style.top = input.style.left = '20px'; testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2113,12 +2253,13 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.left)) .withContext('Expected popup to align to input left.') .toBe(Math.floor(inputRect.left)); - }); + })); - it('should be above and to the right when there is no space below', () => { + it('should be above and to the right when there is no space below', fakeAsync(() => { input.style.bottom = input.style.left = '20px'; testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2129,12 +2270,13 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.left)) .withContext('Expected popup to align to input left.') .toBe(Math.floor(inputRect.left)); - }); + })); - it('should be below and to the left when there is no space on the right', () => { + it('should be below and to the left when there is no space on the right', fakeAsync(() => { input.style.top = input.style.right = '20px'; testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2145,12 +2287,13 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.right)) .withContext('Expected popup to align to input right.') .toBe(Math.floor(inputRect.right)); - }); + })); - it('should be above and to the left when there is no space on the bottom', () => { + it('should be above and to the left when there is no space on the bottom', fakeAsync(() => { input.style.bottom = input.style.right = '20px'; testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2161,9 +2304,9 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.right)) .withContext('Expected popup to align to input right.') .toBe(Math.floor(inputRect.right)); - }); + })); - it('should be able to customize the calendar position along the X axis', () => { + it('should be able to customize the calendar position along the X axis', fakeAsync(() => { input.style.top = input.style.left = '200px'; testComponent.xPosition = 'end'; fixture.changeDetectorRef.markForCheck(); @@ -2171,6 +2314,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2178,9 +2322,9 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.right)) .withContext('Expected popup to align to input right.') .toBe(Math.floor(inputRect.right)); - }); + })); - it('should be able to customize the calendar position along the Y axis', () => { + it('should be able to customize the calendar position along the Y axis', fakeAsync(() => { input.style.bottom = input.style.left = '100px'; testComponent.yPosition = 'above'; fixture.changeDetectorRef.markForCheck(); @@ -2188,6 +2332,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); @@ -2195,7 +2340,7 @@ describe('MatDatepicker', () => { expect(Math.floor(overlayRect.bottom)) .withContext('Expected popup to align to input top.') .toBe(Math.floor(inputRect.top)); - }); + })); }); describe('internationalization', () => { @@ -2213,7 +2358,7 @@ describe('MatDatepicker', () => { input = fixture.nativeElement.querySelector('input') as HTMLInputElement; }); - it('should have the correct input value even when inverted date format', async () => { + it('should have the correct input value even when inverted date format', fakeAsync(() => { if (typeof Intl === 'undefined') { // Skip this test if the internationalization API is not supported in the current // browser. Browsers like Safari 9 do not support the "Intl" API. @@ -2224,45 +2369,49 @@ describe('MatDatepicker', () => { testComponent.date = selected; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); // Normally the proper date format would 01.09.2017, but some browsers seem format the // date without the leading zero. (e.g. 1.9.2017). expect(input.value).toMatch(/0?1\.0?9\.2017/); expect(testComponent.datepickerInput.value).toBe(selected); - }); + })); }); describe('datepicker with custom header', () => { let fixture: ComponentFixture; let testComponent: DatepickerWithCustomHeader; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(DatepickerWithCustomHeader, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - it('should instantiate a datepicker with a custom header', () => { + it('should instantiate a datepicker with a custom header', fakeAsync(() => { expect(testComponent).toBeTruthy(); - }); + })); - it('should find the standard header element', () => { + it('should find the standard header element', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); fixture.detectChanges(); expect(document.querySelector('mat-calendar-header')).toBeTruthy(); - }); + })); - it('should find the custom element', () => { + it('should find the custom element', fakeAsync(() => { testComponent.datepicker.open(); fixture.detectChanges(); + tick(); + flush(); fixture.detectChanges(); expect(document.querySelector('.custom-element')).toBeTruthy(); - }); + })); }); it('should not trigger validators if new date object for same date is set for `min`', () => { @@ -2353,17 +2502,18 @@ describe('MatDatepicker', () => { let fixture: ComponentFixture; let testComponent: PanelClassDatepicker; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = createComponent(PanelClassDatepicker, [provideNativeDateAdapter()]); fixture.detectChanges(); testComponent = fixture.componentInstance; - }); + })); - afterEach(() => { + afterEach(fakeAsync(() => { testComponent.datepicker.close(); fixture.detectChanges(); - }); + flush(); + })); it('should accept a single class', () => { testComponent.panelClass = 'foobar'; @@ -2379,18 +2529,19 @@ describe('MatDatepicker', () => { expect(testComponent.datepicker.panelClass).toEqual(['foo', 'bar']); }); - it('should work with ngClass', () => { + it('should work with ngClass', fakeAsync(() => { testComponent.panelClass = ['foo', 'bar']; fixture.changeDetectorRef.markForCheck(); testComponent.datepicker.open(); fixture.detectChanges(); + tick(); const actualClasses = document.querySelector( '.mat-datepicker-content .mat-calendar', )!.classList; expect(actualClasses.contains('foo')).toBe(true); expect(actualClasses.contains('bar')).toBe(true); - }); + })); }); }); @@ -2423,7 +2574,6 @@ const inputFixedWidthStyles = ` `, styles: inputFixedWidthStyles, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardDatepicker { opened = false; @@ -2443,14 +2593,12 @@ class StandardDatepicker { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiInputDatepicker {} @Component({ template: ``, imports: [MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class NoInputDatepicker { @ViewChild('d') datepicker!: MatDatepicker; @@ -2462,7 +2610,6 @@ class NoInputDatepicker { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithStartAt { date = new Date(2020, JAN, 1); @@ -2476,7 +2623,6 @@ class DatepickerWithStartAt { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithStartViewYear { date = new Date(2020, JAN, 1); @@ -2492,7 +2638,6 @@ class DatepickerWithStartViewYear { (yearSelected)="onMultiYearSelection()"> `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithStartViewMultiYear { date = new Date(2020, JAN, 1); @@ -2507,7 +2652,6 @@ class DatepickerWithStartViewMultiYear { `, imports: [MatDatepickerInput, MatDatepicker, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithNgModel { selected: Date | null = null; @@ -2522,7 +2666,6 @@ class DatepickerWithNgModel { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithFormControl { formControl = new FormControl(null); @@ -2538,7 +2681,6 @@ class DatepickerWithFormControl { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithToggle { @ViewChild('d') datepicker!: MatDatepicker; @@ -2556,7 +2698,6 @@ class DatepickerWithToggle { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithToggleInShadowDom extends DatepickerWithToggle {} @@ -2569,7 +2710,6 @@ class DatepickerWithToggleInShadowDom extends DatepickerWithToggle {} `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithCustomIcon {} @@ -2582,7 +2722,6 @@ class DatepickerWithCustomIcon {} `, imports: [MatDatepickerInput, MatDatepicker, MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class FormFieldDatepicker { @ViewChild('d') datepicker!: MatDatepicker; @@ -2597,7 +2736,6 @@ class FormFieldDatepicker { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithMinAndMaxValidation { @ViewChild('d') datepicker!: MatDatepicker; @@ -2614,7 +2752,6 @@ class DatepickerWithMinAndMaxValidation { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithFilterAndValidation { @ViewChild('d') datepicker!: MatDatepicker; @@ -2630,7 +2767,6 @@ class DatepickerWithFilterAndValidation { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithChangeAndInputEvents { @ViewChild('d') datepicker!: MatDatepicker; @@ -2647,7 +2783,6 @@ class DatepickerWithChangeAndInputEvents { `, imports: [MatDatepickerInput, MatDatepicker, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithi18n { date: Date | null = new Date(2010, JAN, 1); @@ -2661,7 +2796,6 @@ class DatepickerWithi18n { `, imports: [MatDatepickerInput, MatDatepicker, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithISOStrings { value = new Date(2017, JUN, 1).toISOString(); @@ -2678,7 +2812,6 @@ class DatepickerWithISOStrings { `, imports: [MatDatepickerInput, MatDatepicker, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithEvents { selected: Date | null = null; @@ -2693,7 +2826,6 @@ class DatepickerWithEvents { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerOpeningOnFocus { @ViewChild(MatDatepicker) datepicker!: MatDatepicker; @@ -2705,7 +2837,6 @@ class DatepickerOpeningOnFocus { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithCustomHeader { @ViewChild('ch') datepicker!: MatDatepicker; @@ -2718,7 +2849,6 @@ class DatepickerWithCustomHeader { `, imports: [MatCalendarHeader], - changeDetection: ChangeDetectionStrategy.Eager, }) class CustomHeaderForDatepicker {} @@ -2728,7 +2858,6 @@ class CustomHeaderForDatepicker {} `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class DelayedDatepicker { @ViewChild('d') datepicker!: MatDatepicker; @@ -2747,7 +2876,6 @@ class DelayedDatepicker { `, imports: [MatDatepickerInput, MatDatepicker, MatDatepickerToggle, MatDatepickerToggleIcon], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerWithTabindexOnToggle { disabled = false; @@ -2756,14 +2884,12 @@ class DatepickerWithTabindexOnToggle { @Component({ template: ``, imports: [MatDatepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerToggleWithNoDatepicker {} @Component({ template: ``, imports: [MatDatepickerInput], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerInputWithNoDatepicker {} @@ -2787,7 +2913,6 @@ class CustomValidator implements Validator { `, imports: [MatDatepickerInput, MatDatepicker, CustomValidator, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerInputWithCustomValidator { @ViewChild(CustomValidator) validator!: CustomValidator; @@ -2802,7 +2927,6 @@ class DatepickerInputWithCustomValidator { `, imports: [MatDatepickerInput, MatDatepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelClassDatepicker { date = new Date(0); diff --git a/src/material/datepicker/datepicker.ts b/src/material/datepicker/datepicker.ts index 639402be9cab..cdae6ec8f2fa 100644 --- a/src/material/datepicker/datepicker.ts +++ b/src/material/datepicker/datepicker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {MatDatepickerBase, MatDatepickerControl} from './datepicker-base'; import {MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER} from './date-selection-model'; @@ -18,6 +18,7 @@ import {MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER} from './date-selection-model'; selector: 'mat-datepicker', template: '', exportAs: 'matDatepicker', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ MAT_SINGLE_DATE_SELECTION_MODEL_PROVIDER, diff --git a/src/material/datepicker/month-view.spec.ts b/src/material/datepicker/month-view.spec.ts index c444fc394140..f7556cd357d1 100644 --- a/src/material/datepicker/month-view.spec.ts +++ b/src/material/datepicker/month-view.spec.ts @@ -20,7 +20,7 @@ import { dispatchMouseEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import {Component, signal, WritableSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal, WritableSignal} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MAT_DATE_FORMATS, provideNativeDateAdapter} from '../core'; @@ -846,7 +846,6 @@ describe('MatMonthView', () => { [activeDrag]="activeDrag"> `, imports: [MatMonthView], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardMonthView { date = new Date(2017, JAN, 5); @@ -878,7 +877,6 @@ class StandardMonthView { [minDate]="minDate" [maxDate]="maxDate">`, imports: [MatMonthView], - changeDetection: ChangeDetectionStrategy.Eager, }) class MonthViewWithDateFilter { activeDate = new Date(2017, JAN, 1); @@ -892,7 +890,6 @@ class MonthViewWithDateFilter { @Component({ template: ``, imports: [MatMonthView], - changeDetection: ChangeDetectionStrategy.Eager, }) class MonthViewWithDateClass { activeDate = new Date(2017, JAN, 1); diff --git a/src/material/datepicker/month-view.ts b/src/material/datepicker/month-view.ts index 8d248096a9c6..cf21bf7ecda1 100644 --- a/src/material/datepicker/month-view.ts +++ b/src/material/datepicker/month-view.ts @@ -22,6 +22,7 @@ import { } from '@angular/cdk/keycodes'; import { AfterContentInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -66,6 +67,7 @@ let uniqueIdCounter = 0; templateUrl: 'month-view.html', exportAs: 'matMonthView', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatCalendarBody], }) export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { @@ -217,6 +219,8 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { /** The names of the weekdays. */ _weekdays = signal<{long: string; narrow: string; id: number}[]>([]); + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_VisuallyHiddenLoader); if (typeof ngDevMode === 'undefined' || ngDevMode) { @@ -237,7 +241,7 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { .subscribe(() => this._init()); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const comparisonChange = changes['comparisonStart'] || changes['comparisonEnd']; if (comparisonChange && !comparisonChange.firstChange) { diff --git a/src/material/datepicker/multi-year-view.spec.ts b/src/material/datepicker/multi-year-view.spec.ts index 8d4a4864389b..b530f731c7e0 100644 --- a/src/material/datepicker/multi-year-view.spec.ts +++ b/src/material/datepicker/multi-year-view.spec.ts @@ -14,7 +14,7 @@ import { dispatchKeyboardEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import {Component, signal, ViewChild, WritableSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal, ViewChild, WritableSignal} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {provideNativeDateAdapter} from '../core'; @@ -398,7 +398,6 @@ describe('MatMultiYearView', () => { `, imports: [MatMultiYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardMultiYearView { date = new Date(2017, JAN, 1); @@ -417,7 +416,6 @@ class StandardMultiYearView { [maxDate]="maxDate"> `, imports: [MatMultiYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiYearViewWithDateFilter { activeDate = new Date(2017, JAN, 1); @@ -434,7 +432,6 @@ class MultiYearViewWithDateFilter { `, imports: [MatMultiYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiYearViewWithMinMaxDate { activeDate = new Date(2019, JAN, 1); @@ -447,7 +444,6 @@ class MultiYearViewWithMinMaxDate { `, imports: [MatMultiYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class MultiYearViewWithDateClass { activeDate = new Date(2017, JAN, 1); diff --git a/src/material/datepicker/multi-year-view.ts b/src/material/datepicker/multi-year-view.ts index 24211f2d7631..53cd055b03c4 100644 --- a/src/material/datepicker/multi-year-view.ts +++ b/src/material/datepicker/multi-year-view.ts @@ -20,6 +20,7 @@ import { } from '@angular/cdk/keycodes'; import { AfterContentInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -57,6 +58,7 @@ export const yearsPerRow = 4; templateUrl: 'multi-year-view.html', exportAs: 'matMultiYearView', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatCalendarBody], }) export class MatMultiYearView implements AfterContentInit, OnDestroy { @@ -157,6 +159,8 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { /** The year of the selected date. Null if the selected date is null. */ _selectedYear = signal(null); + constructor(...args: unknown[]); + constructor() { if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw createMissingDateImplError('DateAdapter'); diff --git a/src/material/datepicker/testing/BUILD.bazel b/src/material/datepicker/testing/BUILD.bazel index e6dfdf891f12..e492a514b79c 100644 --- a/src/material/datepicker/testing/BUILD.bazel +++ b/src/material/datepicker/testing/BUILD.bazel @@ -31,7 +31,6 @@ ng_project( "//:node_modules/@angular/platform-browser", "//src/cdk/testing", "//src/cdk/testing/testbed", - "//src/material/button", "//src/material/core", "//src/material/datepicker", "//src/material/form-field", diff --git a/src/material/datepicker/testing/calendar-harness.spec.ts b/src/material/datepicker/testing/calendar-harness.spec.ts index 4e58afb54f20..f63d41c97696 100644 --- a/src/material/datepicker/testing/calendar-harness.spec.ts +++ b/src/material/datepicker/testing/calendar-harness.spec.ts @@ -1,6 +1,6 @@ import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNativeDateAdapter} from '../../core'; import { @@ -330,7 +330,6 @@ describe('MatCalendarHarness', () => { (selectedChange)="rangeChanged($event)"> `, imports: [MatDatepickerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class CalendarHarnessTest { // Start the datepickers off at a specific date so tests diff --git a/src/material/datepicker/testing/date-range-input-harness.spec.ts b/src/material/datepicker/testing/date-range-input-harness.spec.ts index 844b0b292386..c83edfc4423a 100644 --- a/src/material/datepicker/testing/date-range-input-harness.spec.ts +++ b/src/material/datepicker/testing/date-range-input-harness.spec.ts @@ -1,6 +1,6 @@ import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FormsModule} from '@angular/forms'; import {MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../../core'; @@ -289,7 +289,6 @@ describe('matDateRangeInputHarness', () => { MatFormFieldModule, FormsModule, ], - changeDetection: ChangeDetectionStrategy.Eager, }) class DateRangeInputHarnessTest { startDate: Date | null = null; diff --git a/src/material/datepicker/testing/datepicker-actions-harness.spec.ts b/src/material/datepicker/testing/datepicker-actions-harness.spec.ts deleted file mode 100644 index ce9437b4c0dc..000000000000 --- a/src/material/datepicker/testing/datepicker-actions-harness.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {HarnessLoader} from '@angular/cdk/testing'; -import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {MatButtonModule} from '../../button'; -import {MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../../core'; -import {MatDatepickerModule} from '../../datepicker'; -import {MatDatepickerActionsHarness} from './datepicker-actions-harness'; -import {MatDatepickerInputHarness} from './datepicker-input-harness'; - -describe('MatDatepickerActionsHarness', () => { - let fixture: ComponentFixture; - let loader: HarnessLoader; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - provideNativeDateAdapter(), - {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, - ], - }); - - fixture = TestBed.createComponent(DatepickerActionsHarnessTest); - loader = TestbedHarnessEnvironment.documentRootLoader(fixture); - }); - - it('should load datepicker actions harness', async () => { - const input = await loader.getHarness(MatDatepickerInputHarness); - await input.openCalendar(); - const actions = await loader.getAllHarnesses(MatDatepickerActionsHarness); - expect(actions.length).toBe(1); - }); - - it('should be able to apply', async () => { - const input = await loader.getHarness(MatDatepickerInputHarness); - await input.openCalendar(); - expect(await input.isCalendarOpen()).toBe(true); - const actions = await loader.getHarness(MatDatepickerActionsHarness); - await actions.apply(); - expect(await input.isCalendarOpen()).toBe(false); - expect(fixture.componentInstance.applied()).toBe(true); - }); - - it('should be able to cancel', async () => { - const input = await loader.getHarness(MatDatepickerInputHarness); - await input.openCalendar(); - expect(await input.isCalendarOpen()).toBe(true); - const actions = await loader.getHarness(MatDatepickerActionsHarness); - await actions.cancel(); - expect(await input.isCalendarOpen()).toBe(false); - expect(fixture.componentInstance.applied()).toBe(false); - }); -}); - -@Component({ - template: ` - - - - - - - - `, - imports: [MatDatepickerModule, MatButtonModule], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class DatepickerActionsHarnessTest { - applied = signal(false); -} diff --git a/src/material/datepicker/testing/datepicker-actions-harness.ts b/src/material/datepicker/testing/datepicker-actions-harness.ts deleted file mode 100644 index 1e59800b6053..000000000000 --- a/src/material/datepicker/testing/datepicker-actions-harness.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - ContentContainerComponentHarness, - HarnessPredicate, - TestElement, -} from '@angular/cdk/testing'; -import {DatepickerActionsHarnessFilters} from './datepicker-harness-filters'; - -/** Harness for interacting with a standard Material datepicker actions in tests. */ -export class MatDatepickerActionsHarness extends ContentContainerComponentHarness { - static hostSelector = '.mat-datepicker-actions'; - - private _applyLocator = this.locatorForOptional( - '[matDatepickerApply], [matDateRangePickerApply]', - ); - private _cancelLocator = this.locatorForOptional( - '[matDatepickerCancel], [matDateRangePickerCancel]', - ); - - /** - * Gets a `HarnessPredicate` that can be used to search for a `MatDatepickerActionsHarness` that - * meets certain criteria. - * @param options Options for filtering which datepicker actions instances are considered a match. - * @return a `HarnessPredicate` configured with the given options. - */ - static with( - options: DatepickerActionsHarnessFilters = {}, - ): HarnessPredicate { - return new HarnessPredicate(MatDatepickerActionsHarness, options); - } - - /** Applies the current selection. */ - apply(): Promise { - return this._clickAction('apply', this._applyLocator); - } - - /** Cancels the current selection. */ - cancel(): Promise { - return this._clickAction('cancel', this._cancelLocator); - } - - private async _clickAction(name: string, locator: () => Promise) { - const button = await locator(); - - if (!button) { - throw new Error(`MatDatepickerActions does not have ${name} button`); - } - - await button.click(); - } -} diff --git a/src/material/datepicker/testing/datepicker-harness-filters.ts b/src/material/datepicker/testing/datepicker-harness-filters.ts index ba783c50356b..90e750fd7ad2 100644 --- a/src/material/datepicker/testing/datepicker-harness-filters.ts +++ b/src/material/datepicker/testing/datepicker-harness-filters.ts @@ -48,6 +48,3 @@ export interface DateRangeInputHarnessFilters extends MatFormFieldControlHarness /** Filters based on the value of the input. */ value?: string | RegExp; } - -/** A set of criteria that can be used to filter a list of datepicker actions instances. */ -export interface DatepickerActionsHarnessFilters extends BaseHarnessFilters {} diff --git a/src/material/datepicker/testing/datepicker-input-harness.spec.ts b/src/material/datepicker/testing/datepicker-input-harness.spec.ts index 93c77e8f1b11..5e4db8bcf05b 100644 --- a/src/material/datepicker/testing/datepicker-input-harness.spec.ts +++ b/src/material/datepicker/testing/datepicker-input-harness.spec.ts @@ -1,6 +1,6 @@ import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FormsModule} from '@angular/forms'; import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../../core'; @@ -217,7 +217,6 @@ describe('MatDatepickerInputHarness', () => { `, imports: [MatDatepickerModule, MatFormFieldModule, MatInputModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerInputHarnessTest { date: Date | null = null; diff --git a/src/material/datepicker/testing/datepicker-toggle-harness.spec.ts b/src/material/datepicker/testing/datepicker-toggle-harness.spec.ts index 079d982dac5a..ed6a825b26db 100644 --- a/src/material/datepicker/testing/datepicker-toggle-harness.spec.ts +++ b/src/material/datepicker/testing/datepicker-toggle-harness.spec.ts @@ -1,6 +1,6 @@ import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../../core'; import {MatDatepickerModule} from '../../datepicker'; @@ -86,7 +86,6 @@ describe('MatDatepickerToggleHarness', () => { `, imports: [MatDatepickerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DatepickerToggleHarnessTest { touchUi = false; diff --git a/src/material/datepicker/testing/public-api.ts b/src/material/datepicker/testing/public-api.ts index ce944a176daf..92ebb012d20b 100644 --- a/src/material/datepicker/testing/public-api.ts +++ b/src/material/datepicker/testing/public-api.ts @@ -12,4 +12,3 @@ export * from './datepicker-toggle-harness'; export * from './date-range-input-harness'; export * from './calendar-harness'; export * from './calendar-cell-harness'; -export * from './datepicker-actions-harness'; diff --git a/src/material/datepicker/year-view.spec.ts b/src/material/datepicker/year-view.spec.ts index 1bb1805758c4..06d59a4c18f0 100644 --- a/src/material/datepicker/year-view.spec.ts +++ b/src/material/datepicker/year-view.spec.ts @@ -14,7 +14,7 @@ import { dispatchKeyboardEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import {Component, signal, ViewChild, WritableSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal, ViewChild, WritableSignal} from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {provideNativeDateAdapter} from '../core'; @@ -395,7 +395,6 @@ describe('MatYearView', () => { `, imports: [MatYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandardYearView { date = new Date(2017, JAN, 5); @@ -413,7 +412,6 @@ class StandardYearView { [minDate]="minDate" [maxDate]="maxDate">`, imports: [MatYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class YearViewWithDateFilter { activeDate = new Date(2017, JAN, 1); @@ -433,7 +431,6 @@ class YearViewWithDateFilter { @Component({ template: ``, imports: [MatYearView], - changeDetection: ChangeDetectionStrategy.Eager, }) class YearViewWithDateClass { activeDate = new Date(2017, JAN, 1); diff --git a/src/material/datepicker/year-view.ts b/src/material/datepicker/year-view.ts index 60765eb90263..10a5d170df9c 100644 --- a/src/material/datepicker/year-view.ts +++ b/src/material/datepicker/year-view.ts @@ -20,6 +20,7 @@ import { } from '@angular/cdk/keycodes'; import { AfterContentInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -53,6 +54,7 @@ import {DateRange} from './date-selection-model'; templateUrl: 'year-view.html', exportAs: 'matYearView', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatCalendarBody], }) export class MatYearView implements AfterContentInit, OnDestroy { @@ -152,6 +154,8 @@ export class MatYearView implements AfterContentInit, OnDestroy { */ _selectedMonth = signal(null); + constructor(...args: unknown[]); + constructor() { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!this._dateAdapter) { diff --git a/src/material/dialog/dialog-config.ts b/src/material/dialog/dialog-config.ts index 0ebf4ee37b68..0cfb0ae86234 100644 --- a/src/material/dialog/dialog-config.ts +++ b/src/material/dialog/dialog-config.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ViewContainerRef, Injector, Binding} from '@angular/core'; +import {ViewContainerRef, Injector} from '@angular/core'; import {Direction} from '@angular/cdk/bidi'; import {ScrollStrategy} from '@angular/cdk/overlay'; -import {DialogConfig, RestoreFocusValue} from '@angular/cdk/dialog'; +import {DialogConfig} from '@angular/cdk/dialog'; /** Options for where to set focus to automatically on dialog open */ export type AutoFocusTarget = 'dialog' | 'first-tabbable' | 'first-heading'; @@ -129,8 +129,11 @@ export class MatDialogConfig { */ autoFocus?: AutoFocusTarget | string | boolean = 'first-tabbable'; - /** Configures the focus restoration behavior. See `RestoreFocusValue` for more information. */ - restoreFocus?: RestoreFocusValue = true; + /** + * Whether the dialog should restore focus to the + * previously-focused element, after it's closed. + */ + restoreFocus?: boolean = true; /** Whether to wait for the opening animation to finish before trapping focus. */ delayFocusTrap?: boolean = true; @@ -159,9 +162,5 @@ export class MatDialogConfig { */ exitAnimationDuration?: string | number; - /** - * Bindings to apply to the component rendered inside the dialog. - * Does nothing for template-based dialogs. - */ - bindings?: Binding[]; + // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling. } diff --git a/src/material/dialog/dialog-container.ts b/src/material/dialog/dialog-container.ts index dd3cf5b51dab..c217c6aca5d7 100644 --- a/src/material/dialog/dialog-container.ts +++ b/src/material/dialog/dialog-container.ts @@ -48,7 +48,7 @@ export const CLOSE_ANIMATION_DURATION = 75; encapsulation: ViewEncapsulation.None, // Disabled for consistency with the non-MDC dialog container. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [CdkPortalOutlet], host: { 'class': 'mat-mdc-dialog-container mdc-dialog', diff --git a/src/material/dialog/dialog-content-directives.ts b/src/material/dialog/dialog-content-directives.ts index 190bcb2664b4..76c65a9bf47d 100644 --- a/src/material/dialog/dialog-content-directives.ts +++ b/src/material/dialog/dialog-content-directives.ts @@ -50,6 +50,9 @@ export class MatDialogClose implements OnInit, OnChanges { @Input('matDialogClose') _matDialogClose: any; + constructor(...args: unknown[]); + constructor() {} + ngOnInit() { if (!this.dialogRef) { // When this directive is included in a dialog via TemplateRef (rather than being @@ -61,8 +64,8 @@ export class MatDialogClose implements OnInit, OnChanges { } } - ngOnChanges(changes: SimpleChanges) { - const proxiedChange = changes['_matDialogClose']; + ngOnChanges(changes: SimpleChanges) { + const proxiedChange = changes['_matDialogClose'] || changes['_matDialogCloseResult']; if (proxiedChange) { this.dialogResult = proxiedChange.currentValue; @@ -70,12 +73,6 @@ export class MatDialogClose implements OnInit, OnChanges { } _onButtonClick(event: MouseEvent) { - // For compatibility with `disabledInteractive`. We don't need to handle plain `disabled`, - // because disabled buttons don't dispatch click events by default. See #33366. - if (this._elementRef.nativeElement.getAttribute('aria-disabled') === 'true') { - return; - } - // Determinate the focus origin using the click event, because using the FocusMonitor will // result in incorrect origins. Most of the time, close buttons will be auto focused in the // dialog, and therefore clicking the button won't result in a focus change. This means that @@ -94,6 +91,10 @@ export abstract class MatDialogLayoutSection implements OnInit, OnDestroy { private _elementRef = inject>(ElementRef); private _dialog = inject(MatDialog); + constructor(...args: unknown[]); + + constructor() {} + protected abstract _onAdd(): void; protected abstract _onRemove(): void; diff --git a/src/material/dialog/dialog.md b/src/material/dialog/dialog.md index 1424d81c9270..5b58567cfdc1 100644 --- a/src/material/dialog/dialog.md +++ b/src/material/dialog/dialog.md @@ -29,11 +29,9 @@ in which they are contained. When closing, an optional result value can be provi value is forwarded as the result of the `afterClosed` Observable. ```ts -import {inject} from '@angular/core'; - @Component({/* ... */}) export class YourDialog { - dialogRef = inject(MatDialogRef); + constructor(public dialogRef: MatDialogRef) { } closeDialog() { this.dialogRef.close('Pizza!'); @@ -43,28 +41,16 @@ export class YourDialog { ### Specifying global configuration defaults Default dialog options can be specified by providing an instance of `MatDialogConfig` for -`MAT_DIALOG_DEFAULT_OPTIONS` in your app config. +MAT_DIALOG_DEFAULT_OPTIONS in your application's root module. ```ts -bootstrapApplication(MyApp, { +@NgModule({ providers: [ {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: false}} ] -}); +}) ``` -> **Note:** The value provided for `MAT_DIALOG_DEFAULT_OPTIONS` **replaces** the built-in defaults -> entirely rather than merging with them. For example, providing `{disableClose: true}` means that -> all other defaults (such as `hasBackdrop`) will be `undefined`. If you only want to override -> specific properties, spread the defaults first: -> -> ```ts -> {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {...new MatDialogConfig(), disableClose: true}} -> ``` -> -> When you call `dialog.open()` with a config, that config is merged on top of these defaults, so -> per-dialog options always take precedence. - ### Sharing data with the Dialog component. If you want to share data with your dialog, you can use the `data` option to pass information to the dialog component. @@ -78,15 +64,15 @@ let dialogRef = dialog.open(YourDialog, { To access the data in your dialog component, you have to use the MAT_DIALOG_DATA injection token: ```ts -import {Component, inject} from '@angular/core'; -import {MAT_DIALOG_DATA} from '@angular/material/dialog'; +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA} from '../dialog'; @Component({ selector: 'your-dialog', template: 'passed in {{ data.name }}', }) export class YourDialog { - data = inject<{name: string}>(MAT_DIALOG_DATA); + constructor(@Inject(MAT_DIALOG_DATA) public data: {name: string}) { } } ``` diff --git a/src/material/dialog/dialog.spec.ts b/src/material/dialog/dialog.spec.ts index 47ba393a2ba5..23b687650a0e 100644 --- a/src/material/dialog/dialog.spec.ts +++ b/src/material/dialog/dialog.spec.ts @@ -16,11 +16,10 @@ import {SpyLocation} from '@angular/common/testing'; import { ChangeDetectionStrategy, Component, - createNgModule, + createNgModuleRef, Directive, - Service, + Injectable, Injector, - Input, NgModule, TemplateRef, ViewChild, @@ -29,9 +28,15 @@ import { forwardRef, signal, inject, - inputBinding, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + flushMicrotasks, + tick, +} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {map} from 'rxjs/operators'; @@ -59,7 +64,7 @@ describe('MatDialog', () => { let viewContainerFixture: ComponentFixture; let mockLocation: SpyLocation; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ {provide: Location, useClass: SpyLocation}, @@ -82,7 +87,7 @@ describe('MatDialog', () => { viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; - }); + })); it('should open a dialog with a component', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); @@ -134,7 +139,7 @@ describe('MatDialog', () => { dialogRef.close(); }); - it('should emit when dialog opening animation is complete', async () => { + it('should emit when dialog opening animation is complete', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterOpen spy'); @@ -145,9 +150,9 @@ describe('MatDialog', () => { // callback should not be called before animation is complete expect(spy).not.toHaveBeenCalled(); - await viewContainerFixture.whenStable(); + flush(); expect(spy).toHaveBeenCalled(); - }); + })); it('should use injector from viewContainerRef for DialogInjector', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); @@ -197,31 +202,31 @@ describe('MatDialog', () => { expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element'); }); - it('should close a dialog and get back a result', async () => { + it('should close a dialog and get back a result', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); let afterCloseCallback = jasmine.createSpy('afterClose callback'); dialogRef.afterClosed().subscribe(afterCloseCallback); dialogRef.close('Charmander'); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(afterCloseCallback).toHaveBeenCalledWith('Charmander'); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); - }); + })); - it('should dispose of dialog if view container is destroyed while animating', async () => { + it('should dispose of dialog if view container is destroyed while animating', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); dialogRef.close(); viewContainerFixture.detectChanges(); viewContainerFixture.destroy(); - await wait(100); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); - }); + })); - it('should dispatch the beforeClosed and afterClosed events when the overlay is detached externally', async () => { + it('should dispatch the beforeClosed and afterClosed events when the overlay is detached externally', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, scrollStrategy: createCloseScrollStrategy(TestBed.inject(Injector)), @@ -234,16 +239,16 @@ describe('MatDialog', () => { scrolledSubject.next(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(beforeClosedCallback).toHaveBeenCalledTimes(1); expect(afterCloseCallback).toHaveBeenCalledTimes(1); - }); + })); - it('should close a dialog and get back a result before it is closed', async () => { + it('should close a dialog and get back a result before it is closed', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); // beforeClose should emit before dialog container is destroyed @@ -256,36 +261,36 @@ describe('MatDialog', () => { dialogRef.beforeClosed().subscribe(beforeCloseHandler); dialogRef.close('Bulbasaur'); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaur'); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); - }); + })); - it('should close a dialog via the escape key', async () => { + it('should close a dialog via the escape key', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not close a dialog via the escape key with a modifier', async () => { + it('should not close a dialog via the escape key with a modifier', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true}); dispatchEvent(document.body, event); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should close from a ViewContainerRef with OnPush change detection', async () => { + it('should close from a ViewContainerRef with OnPush change detection', fakeAsync(() => { const onPushFixture = TestBed.createComponent(ComponentWithOnPushViewContainer); onPushFixture.detectChanges(); @@ -294,25 +299,25 @@ describe('MatDialog', () => { viewContainerRef: onPushFixture.componentInstance.viewContainerRef, }); - await onPushFixture.whenStable(); + flushMicrotasks(); onPushFixture.detectChanges(); - await onPushFixture.whenStable(); + flushMicrotasks(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length) .withContext('Expected one open dialog.') .toBe(1); dialogRef.close(); - await onPushFixture.whenStable(); + flushMicrotasks(); onPushFixture.detectChanges(); - await wait(100); + tick(500); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length) .withContext('Expected no open dialogs.') .toBe(0); - }); + })); - it('should close when clicking on the overlay backdrop', async () => { + it('should close when clicking on the overlay backdrop', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); @@ -321,12 +326,12 @@ describe('MatDialog', () => { backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); - }); + })); - it('should emit the backdropClick stream when clicking on the overlay backdrop', async () => { + it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('backdropClick spy'); @@ -340,21 +345,21 @@ describe('MatDialog', () => { expect(spy).toHaveBeenCalledTimes(1); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); // Additional clicks after the dialog has closed should not be emitted backdrop.click(); expect(spy).toHaveBeenCalledTimes(1); - }); + })); - it('should emit the keyboardEvent stream when key events target the overlay', async () => { + it('should emit the keyboardEvent stream when key events target the overlay', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('keyboardEvent spy'); dialogRef.keydownEvents().subscribe(spy); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let container = overlayContainerElement.querySelector('mat-dialog-container') as HTMLElement; @@ -363,7 +368,7 @@ describe('MatDialog', () => { dispatchKeyboardEvent(container, 'keydown', A); expect(spy).toHaveBeenCalledTimes(3); - }); + })); it('should notify the observers if a dialog has been opened', () => { dialog.afterOpened.subscribe(ref => { @@ -371,7 +376,7 @@ describe('MatDialog', () => { }); }); - it('should notify the observers if all open dialogs have finished closing', async () => { + it('should notify the observers if all open dialogs have finished closing', fakeAsync(() => { const ref1 = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const ref2 = dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterAllClosed spy'); @@ -380,15 +385,15 @@ describe('MatDialog', () => { ref1.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(spy).not.toHaveBeenCalled(); ref2.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(spy).toHaveBeenCalled(); - }); + })); it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => { const spy = jasmine.createSpy('afterAllClosed spy'); @@ -428,7 +433,7 @@ describe('MatDialog', () => { expect(overlayPane.style.minWidth).toBe('500px'); }); - it('should override the max-width of the overlay pane', async () => { + it('should override the max-width of the overlay pane', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); viewContainerFixture.detectChanges(); @@ -438,18 +443,18 @@ describe('MatDialog', () => { expect(overlayPane.style.maxWidth).toBe(''); dialogRef.close(); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); dialogRef = dialog.open(PizzaMsg, {maxWidth: '100px'}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; expect(overlayPane.style.maxWidth).toBe('100px'); - }); + })); it('should override the min-height of the overlay pane', () => { dialog.open(PizzaMsg, {minHeight: '300px'}); @@ -586,22 +591,22 @@ describe('MatDialog', () => { expect(dialogRef.componentInstance.directionality.value).toBe('ltr'); }); - it('should use the passed in ViewContainerRef from the config', async () => { + it('should use the passed in ViewContainerRef from the config', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); // One view ref is for the container and one more for the component with the content. expect(testViewContainerRef.length).toBe(2); dialogRef.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(testViewContainerRef.length).toBe(0); - }); + })); - it('should close all of the dialogs', async () => { + it('should close all of the dialogs', fakeAsync(() => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); dialog.open(PizzaMsg); @@ -610,12 +615,12 @@ describe('MatDialog', () => { dialog.closeAll(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); - }); + })); - it('should close all dialogs when the user goes forwards/backwards in history', async () => { + it('should close all dialogs when the user goes forwards/backwards in history', fakeAsync(() => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); @@ -623,12 +628,12 @@ describe('MatDialog', () => { mockLocation.simulateUrlPop(''); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); - }); + })); - it('should close all open dialogs when the location hash changes', async () => { + it('should close all open dialogs when the location hash changes', fakeAsync(() => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); @@ -636,12 +641,12 @@ describe('MatDialog', () => { mockLocation.simulateHashChange(''); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); - }); + })); - it('should close all of the dialogs when the injectable is destroyed', async () => { + it('should close all of the dialogs when the injectable is destroyed', fakeAsync(() => { dialog.open(PizzaMsg); dialog.open(PizzaMsg); dialog.open(PizzaMsg); @@ -650,12 +655,12 @@ describe('MatDialog', () => { dialog.ngOnDestroy(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0); - }); + })); - it('should complete open and close streams when the injectable is destroyed', async () => { + it('should complete open and close streams when the injectable is destroyed', fakeAsync(() => { const afterOpenedSpy = jasmine.createSpy('after opened spy'); const afterAllClosedSpy = jasmine.createSpy('after all closed spy'); const afterOpenedSubscription = dialog.afterOpened.subscribe({complete: afterOpenedSpy}); @@ -670,9 +675,9 @@ describe('MatDialog', () => { afterOpenedSubscription.unsubscribe(); afterAllClosedSubscription.unsubscribe(); - }); + })); - it('should allow the consumer to disable closing a dialog on navigation', async () => { + it('should allow the consumer to disable closing a dialog on navigation', fakeAsync(() => { dialog.open(PizzaMsg); dialog.open(PizzaMsg, {closeOnNavigation: false}); @@ -680,18 +685,18 @@ describe('MatDialog', () => { mockLocation.simulateUrlPop(''); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(1); - }); + })); - it('should have the componentInstance available in the afterClosed callback', async () => { + it('should have the componentInstance available in the afterClosed callback', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); let spy = jasmine.createSpy('afterClosed spy'); - await viewContainerFixture.whenStable(); + flushMicrotasks(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); dialogRef.afterClosed().subscribe(() => { spy(); @@ -702,15 +707,15 @@ describe('MatDialog', () => { dialogRef.close(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); // Ensure that the callback actually fires. expect(spy).toHaveBeenCalled(); - }); + })); - it('should be able to attach a custom scroll strategy', async () => { + it('should be able to attach a custom scroll strategy', fakeAsync(() => { const scrollStrategy: ScrollStrategy = { attach: () => {}, enable: jasmine.createSpy('scroll strategy enable spy'), @@ -718,9 +723,9 @@ describe('MatDialog', () => { }; dialog.open(PizzaMsg, {scrollStrategy}); - await viewContainerFixture.whenStable(); + flush(); expect(scrollStrategy.enable).toHaveBeenCalled(); - }); + })); describe('passing in data', () => { it('should be able to pass in data', () => { @@ -738,40 +743,31 @@ describe('MatDialog', () => { expect(dialogRef.componentInstance.data).toBeNull(); }).not.toThrow(); }); - - it('should be able to apply bindings', () => { - const dialogRef = dialog.open(PizzaMsg, { - bindings: [inputBinding('flavor', () => 'pepperoni')], - }); - viewContainerFixture.detectChanges(); - - expect(dialogRef.componentInstance!.flavor).toBe('pepperoni'); - }); }); - it('should not keep a reference to the component after the dialog is closed', async () => { + it('should not keep a reference to the component after the dialog is closed', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg); expect(dialogRef.componentInstance).toBeTruthy(); dialogRef.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(dialogRef.componentInstance) .withContext('Expected reference to have been cleared.') .toBeFalsy(); - }); + })); - it('should assign a unique id to each dialog', async () => { + it('should assign a unique id to each dialog', fakeAsync(() => { const one = dialog.open(PizzaMsg); const two = dialog.open(PizzaMsg); - await viewContainerFixture.whenStable(); + flush(); expect(one.id).toBeTruthy(); expect(two.id).toBeTruthy(); expect(one.id).not.toBe(two.id); - }); + })); it('should allow for the id to be overwritten', () => { const dialogRef = dialog.open(PizzaMsg, {id: 'pizza'}); @@ -788,14 +784,13 @@ describe('MatDialog', () => { expect(dialog.getDialogById('pizza')).toBe(dialogRef); }); - it('should toggle `aria-hidden` on the overlay container siblings', async () => { + it('should toggle `aria-hidden` on the overlay container siblings', fakeAsync(() => { const sibling = document.createElement('div'); overlayContainerElement.parentNode!.appendChild(sibling); const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); - viewContainerFixture.detectChanges(); + flush(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to be hidden') @@ -806,15 +801,15 @@ describe('MatDialog', () => { dialogRef.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(sibling.hasAttribute('aria-hidden')) .withContext('Expected sibling to no longer be hidden.') .toBe(false); sibling.remove(); - }); + })); - it('should restore `aria-hidden` to the overlay container siblings on close', async () => { + it('should restore `aria-hidden` to the overlay container siblings on close', fakeAsync(() => { const sibling = document.createElement('div'); sibling.setAttribute('aria-hidden', 'true'); @@ -822,7 +817,7 @@ describe('MatDialog', () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to be hidden.') @@ -830,15 +825,15 @@ describe('MatDialog', () => { dialogRef.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(sibling.getAttribute('aria-hidden')) .withContext('Expected sibling to remain hidden.') .toBe('true'); sibling.remove(); - }); + })); - it('should not set `aria-hidden` on `aria-live` elements', async () => { + it('should not set `aria-hidden` on `aria-live` elements', fakeAsync(() => { const sibling = document.createElement('div'); sibling.setAttribute('aria-live', 'polite'); @@ -846,13 +841,13 @@ describe('MatDialog', () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(sibling.hasAttribute('aria-hidden')) .withContext('Expected live element not to be hidden.') .toBe(false); sibling.remove(); - }); + })); it('should add and remove classes while open', () => { let dialogRef = dialog.open(PizzaMsg, { @@ -874,7 +869,7 @@ describe('MatDialog', () => { }); describe('disableClose option', () => { - it('should prevent closing via clicks on the backdrop', async () => { + it('should prevent closing via clicks on the backdrop', fakeAsync(() => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); @@ -882,23 +877,23 @@ describe('MatDialog', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); - }); + })); - it('should prevent closing via the escape key', async () => { + it('should prevent closing via the escape key', fakeAsync(() => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy(); - }); + })); - it('should allow for the disableClose option to be updated while open', async () => { + it('should allow for the disableClose option to be updated while open', fakeAsync(() => { let dialogRef = dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, @@ -914,18 +909,18 @@ describe('MatDialog', () => { dialogRef.disableClose = false; backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); - }); + })); - it('should recapture focus when clicking on the backdrop', async () => { + it('should recapture focus when clicking on the backdrop', fakeAsync(() => { dialog.open(PizzaMsg, {disableClose: true, viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let input = overlayContainerElement.querySelector('input') as HTMLInputElement; @@ -937,26 +932,26 @@ describe('MatDialog', () => { input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); - }); + })); it( 'should recapture focus to the first tabbable element when clicking on the backdrop with ' + 'autoFocus set to "first-tabbable" (the default)', - async () => { + fakeAsync(() => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); let backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', @@ -970,18 +965,18 @@ describe('MatDialog', () => { input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); - }, + }), ); it( 'should recapture focus to the container when clicking on the backdrop with ' + 'autoFocus set to "dialog"', - async () => { + fakeAsync(() => { dialog.open(PizzaMsg, { disableClose: true, viewContainerRef: testViewContainerRef, @@ -989,7 +984,7 @@ describe('MatDialog', () => { }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector( @@ -1006,12 +1001,12 @@ describe('MatDialog', () => { container.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected container to stay focused after click') .toBe(container); - }, + }), ); }); @@ -1020,7 +1015,7 @@ describe('MatDialog', () => { return overlayContainerElement.querySelectorAll('mat-dialog-container'); } - it('should determine whether closing via the backdrop is allowed', async () => { + it('should determine whether closing via the backdrop is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -1036,7 +1031,7 @@ describe('MatDialog', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -1044,13 +1039,13 @@ describe('MatDialog', () => { canClose = true; backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should determine whether closing via the escape key is allowed', async () => { + it('should determine whether closing via the escape key is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -1065,7 +1060,7 @@ describe('MatDialog', () => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -1073,13 +1068,13 @@ describe('MatDialog', () => { canClose = true; dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); - }); + })); - it('should determine whether closing via the `close` method is allowed', async () => { + it('should determine whether closing via the `close` method is allowed', fakeAsync(() => { let canClose = false; const closedSpy = jasmine.createSpy('closed spy'); const ref = dialog.open(PizzaMsg, { @@ -1094,7 +1089,7 @@ describe('MatDialog', () => { ref.close(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(1); expect(closedSpy).not.toHaveBeenCalled(); @@ -1102,14 +1097,14 @@ describe('MatDialog', () => { canClose = true; ref.close('hello'); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(0); expect(closedSpy).toHaveBeenCalledTimes(1); expect(closedSpy).toHaveBeenCalledWith('hello'); - }); + })); - it('should not be closed by `closeAll` if not allowed by the predicate', async () => { + it('should not be closed by `closeAll` if not allowed by the predicate', fakeAsync(() => { let canClose = false; const config = {closePredicate: () => canClose}; const spy = jasmine.createSpy('afterAllClosed spy'); @@ -1126,7 +1121,7 @@ describe('MatDialog', () => { dialog.closeAll(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(3); expect(dialog.openDialogs.length).toBe(3); @@ -1135,16 +1130,16 @@ describe('MatDialog', () => { canClose = true; dialog.closeAll(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(getDialogs().length).toBe(0); expect(dialog.openDialogs.length).toBe(0); expect(spy).toHaveBeenCalledTimes(1); subscription.unsubscribe(); - }); + })); - it('should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence', async () => { + it('should recapture focus to the first tabbable element when clicking on the backdrop while the `closePredicate` is blocking the close sequence', fakeAsync(() => { // When testing focus, all of the elements must be in the DOM. document.body.appendChild(overlayContainerElement); @@ -1154,9 +1149,9 @@ describe('MatDialog', () => { }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); const backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', @@ -1170,27 +1165,27 @@ describe('MatDialog', () => { input.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected input to stay focused after click') .toBe(input); overlayContainerElement.remove(); - }); + })); }); it( 'should recapture focus to the first header when clicking on the backdrop with ' + 'autoFocus set to "first-heading"', - async () => { + fakeAsync(() => { dialog.open(ContentElementDialog, { disableClose: true, viewContainerRef: testViewContainerRef, autoFocus: 'first-heading', }); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; @@ -1205,25 +1200,25 @@ describe('MatDialog', () => { firstHeader.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected first header to stay focused after click') .toBe(firstHeader); - }, + }), ); it( 'should recapture focus to the first element that matches the css selector when ' + 'clicking on the backdrop with autoFocus set to a css selector', - async () => { + fakeAsync(() => { dialog.open(ContentElementDialog, { disableClose: true, viewContainerRef: testViewContainerRef, autoFocus: 'button', }); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; @@ -1238,12 +1233,12 @@ describe('MatDialog', () => { firstButton.blur(); // Programmatic clicks might not move focus so we simulate it. backdrop.click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement) .withContext('Expected first button to stay focused after click') .toBe(firstButton); - }, + }), ); it('should be able to use afterOpened in the template while animations are disabled', async () => { @@ -1311,40 +1306,27 @@ describe('MatDialog', () => { beforeEach(() => document.body.appendChild(overlayContainerElement)); afterEach(() => overlayContainerElement.remove()); - it('should focus the first focusable element on open', async () => { - dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - - viewContainerFixture.detectChanges(); - await wait(0); - viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); - - expect(document.activeElement!.tagName) - .withContext('Expected input to be focused') - .toBe('INPUT'); - }); - - it('should focus the first tabbable element of the dialog on open (the default)', async () => { + it('should focus the first tabbable element of the dialog on open (the default)', fakeAsync(() => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement!.tagName) .withContext('Expected first tabbable element (input) in the dialog to be focused.') .toBe('INPUT'); - }); + })); - it('should focus the dialog element on open', async () => { + it('should focus the dialog element on open', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'dialog', }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let container = overlayContainerElement.querySelector( @@ -1354,15 +1336,15 @@ describe('MatDialog', () => { expect(document.activeElement) .withContext('Expected container to be focused on open') .toBe(container); - }); + })); - it('should focus the first header element on open', async () => { + it('should focus the first header element on open', fakeAsync(() => { dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef, autoFocus: 'first-heading', }); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let firstHeader = overlayContainerElement.querySelector( @@ -1372,16 +1354,16 @@ describe('MatDialog', () => { expect(document.activeElement) .withContext('Expected first header to be focused on open') .toBe(firstHeader); - }); + })); - it('should focus the first element that matches the css selector from autoFocus on open', async () => { + it('should focus the first element that matches the css selector from autoFocus on open', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'p', }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); let firstParagraph = overlayContainerElement.querySelector( @@ -1391,23 +1373,23 @@ describe('MatDialog', () => { expect(document.activeElement) .withContext('Expected first paragraph to be focused on open') .toBe(firstParagraph); - }); + })); - it('should attach the focus trap even if automatic focus is disabled', async () => { + it('should attach the focus trap even if automatic focus is disabled', fakeAsync(() => { dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef, autoFocus: 'false', }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect( overlayContainerElement.querySelectorAll('.cdk-focus-trap-anchor').length, ).toBeGreaterThan(0); - }); + })); - it('should re-focus trigger element when dialog closes', async () => { + it('should re-focus trigger element when dialog closes', fakeAsync(() => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1416,7 +1398,7 @@ describe('MatDialog', () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( @@ -1425,17 +1407,22 @@ describe('MatDialog', () => { ); dialogRef.close(); + expect(document.activeElement!.id).not.toBe( + 'dialog-trigger', + 'Expected the focus not to have changed before the animation finishes.', + ); + viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(document.activeElement!.id) - .withContext('Expected focus to be restored to trigger element after dialog is closed.') + .withContext('Expected that the trigger was refocused after the dialog is closed.') .toBe('dialog-trigger'); button.remove(); - }); + })); - it('should re-focus trigger element inside the shadow DOM when dialog closes', async () => { + it('should re-focus trigger element inside the shadow DOM when dialog closes', fakeAsync(() => { if (!_supportsShadowDom()) { return; } @@ -1443,24 +1430,24 @@ describe('MatDialog', () => { viewContainerFixture.destroy(); const fixture = TestBed.createComponent(ShadowDomComponent); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const button = fixture.debugElement.query(By.css('button'))!.nativeElement; button.focus(); const dialogRef = dialog.open(PizzaMsg); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const spy = spyOn(button, 'focus').and.callThrough(); dialogRef.close(); fixture.detectChanges(); - await wait(100); + tick(500); expect(spy).toHaveBeenCalled(); - }); + })); - it('should re-focus the trigger via keyboard when closed via escape key', async () => { + it('should re-focus the trigger via keyboard when closed via escape key', fakeAsync(() => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; @@ -1476,16 +1463,16 @@ describe('MatDialog', () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - await viewContainerFixture.whenStable(); + flushMicrotasks(); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via keyboard') @@ -1493,9 +1480,9 @@ describe('MatDialog', () => { focusMonitor.stopMonitoring(button); button.remove(); - }); + })); - it('should re-focus the trigger via mouse when backdrop has been clicked', async () => { + it('should re-focus the trigger via mouse when backdrop has been clicked', fakeAsync(() => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; @@ -1511,9 +1498,9 @@ describe('MatDialog', () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const backdrop = overlayContainerElement.querySelector( @@ -1522,7 +1509,7 @@ describe('MatDialog', () => { backdrop.click(); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via mouse') @@ -1530,9 +1517,9 @@ describe('MatDialog', () => { focusMonitor.stopMonitoring(button); button.remove(); - }); + })); - it('should re-focus via keyboard if the close button has been triggered through keyboard', async () => { + it('should re-focus via keyboard if the close button has been triggered through keyboard', fakeAsync(() => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; @@ -1548,9 +1535,9 @@ describe('MatDialog', () => { dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const closeButton = overlayContainerElement.querySelector( @@ -1562,7 +1549,7 @@ describe('MatDialog', () => { dispatchMouseEvent(closeButton, 'click', 0, 0); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via keyboard') @@ -1570,9 +1557,9 @@ describe('MatDialog', () => { focusMonitor.stopMonitoring(button); button.remove(); - }); + })); - it('should re-focus via mouse if the close button has been clicked', async () => { + it('should re-focus via mouse if the close button has been clicked', fakeAsync(() => { const button = document.createElement('button'); let lastFocusOrigin: FocusOrigin = null; @@ -1588,9 +1575,9 @@ describe('MatDialog', () => { dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flushMicrotasks(); expect(lastFocusOrigin!).withContext('Expected the trigger button to be blurred').toBeNull(); const closeButton = overlayContainerElement.querySelector( @@ -1603,7 +1590,7 @@ describe('MatDialog', () => { dispatchMouseEvent(closeButton, 'click', 10, 10); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(lastFocusOrigin!) .withContext('Expected the trigger button to be focused via mouse') @@ -1611,9 +1598,9 @@ describe('MatDialog', () => { focusMonitor.stopMonitoring(button); button.remove(); - }); + })); - it('should allow the consumer to shift focus in afterClosed', async () => { + it('should allow the consumer to shift focus in afterClosed', fakeAsync(() => { // Create a element that has focus before the dialog is opened. let button = document.createElement('button'); let input = document.createElement('input'); @@ -1627,15 +1614,15 @@ describe('MatDialog', () => { let dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); dialogRef.afterClosed().subscribe(() => input.focus()); dialogRef.close(); - await wait(100); + tick(500); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement!.id) .withContext('Expected that the trigger was refocused after the dialog is closed.') @@ -1643,23 +1630,23 @@ describe('MatDialog', () => { button.remove(); input.remove(); - await viewContainerFixture.whenStable(); - }); + flush(); + })); - it('should move focus to the container if there are no focusable elements in the dialog', async () => { + it('should move focus to the container if there are no focusable elements in the dialog', fakeAsync(() => { dialog.open(DialogWithoutFocusableElements); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement!.tagName) .withContext('Expected dialog container to be focused.') .toBe('MAT-DIALOG-CONTAINER'); - }); + })); - it('should be able to disable focus restoration', async () => { + it('should be able to disable focus restoration', fakeAsync(() => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); button.id = 'dialog-trigger'; @@ -1671,7 +1658,7 @@ describe('MatDialog', () => { restoreFocus: false, }); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( @@ -1681,7 +1668,7 @@ describe('MatDialog', () => { dialogRef.close(); viewContainerFixture.detectChanges(); - await wait(100); + tick(500); expect(document.activeElement!.id).not.toBe( 'dialog-trigger', @@ -1689,9 +1676,9 @@ describe('MatDialog', () => { ); button.remove(); - }); + })); - it('should not move focus if it was moved outside the dialog while animating', async () => { + it('should not move focus if it was moved outside the dialog while animating', fakeAsync(() => { // Create a element that has focus before the dialog is opened. const button = document.createElement('button'); const otherButton = document.createElement('button'); @@ -1704,7 +1691,7 @@ describe('MatDialog', () => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(document.activeElement!.id).not.toBe( @@ -1721,7 +1708,7 @@ describe('MatDialog', () => { .toBe('other-button'); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(document.activeElement!.id) .withContext('Expected focus to stay on the alternate button.') @@ -1729,7 +1716,7 @@ describe('MatDialog', () => { button.remove(); otherButton.remove(); - }); + })); }); describe('dialog content elements', () => { @@ -1737,18 +1724,18 @@ describe('MatDialog', () => { let hostInstance: ContentElementDialog | ComponentWithContentElementTemplateRef; describe('inside component dialog', () => { - beforeEach(async () => { + beforeEach(fakeAsync(() => { dialogRef = dialog.open(ContentElementDialog, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); hostInstance = dialogRef.componentInstance; - }); + })); runContentElementTests(); }); describe('inside template portal', () => { - beforeEach(async () => { + beforeEach(fakeAsync(() => { const fixture = TestBed.createComponent(ComponentWithContentElementTemplateRef); fixture.detectChanges(); @@ -1757,18 +1744,17 @@ describe('MatDialog', () => { }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); hostInstance = fixture.componentInstance; - }); + })); runContentElementTests(); }); - it('should set the aria-labelledby attribute to the id of the title under OnPush host', async () => { + it('should set the aria-labelledby attribute to the id of the title under OnPush host', fakeAsync(() => { @Component({ imports: [MatDialogTitle], template: `@if (showTitle()) {

    This is the first title

    }`, - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogCmp { showTitle = signal(true); @@ -1777,7 +1763,6 @@ describe('MatDialog', () => { @Component({ template: '', selector: 'child', - changeDetection: ChangeDetectionStrategy.Eager, }) class Child { readonly viewContainerRef = inject(ViewContainerRef); @@ -1793,6 +1778,7 @@ describe('MatDialog', () => { @Component({ imports: [Child], template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, }) class OnPushHost { @ViewChild(Child, {static: true}) child!: Child; @@ -1801,7 +1787,7 @@ describe('MatDialog', () => { const hostFixture = TestBed.createComponent(OnPushHost); hostFixture.componentInstance.child.open(); hostFixture.detectChanges(); - await hostFixture.whenStable(); + flush(); hostFixture.detectChanges(); const overlayContainer = TestBed.inject(OverlayContainer); @@ -1815,44 +1801,25 @@ describe('MatDialog', () => { hostFixture.componentInstance.child.dialogRef?.componentInstance.showTitle.set(false); hostFixture.detectChanges(); - await hostFixture.whenStable(); + flush(); hostFixture.detectChanges(); expect(container.getAttribute('aria-labelledby')).toBe(null); - }); + })); function runContentElementTests() { - it('should close the dialog when clicking on the close button', async () => { + it('should close the dialog when clicking on the close button', fakeAsync(() => { expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 1, ); (overlayContainerElement.querySelector('button[mat-dialog-close]') as HTMLElement).click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( 0, ); - }); - - it('should not close when clicking on an aria-disabled close button', async () => { - expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( - 1, - ); - - const closeButton = overlayContainerElement.querySelector( - 'button[mat-dialog-close]', - ) as HTMLElement; - - closeButton.setAttribute('aria-disabled', 'true'); - closeButton.click(); - viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); - - expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( - 1, - ); - }); + })); it('should not close if [mat-dialog-close] is applied on a non-button node', () => { expect(overlayContainerElement.querySelectorAll('.mat-mdc-dialog-container').length).toBe( @@ -1866,10 +1833,10 @@ describe('MatDialog', () => { ); }); - it('should allow for a user-specified aria-label on the close button', async () => { + it('should allow for a user-specified aria-label on the close button', fakeAsync(() => { let button = overlayContainerElement.querySelector('.close-with-aria-label')!; expect(button.getAttribute('aria-label')).toBe('Best close button ever'); - }); + })); it('should set the "type" attribute of the close button if not set manually', () => { let button = overlayContainerElement.querySelector('button[mat-dialog-close]')!; @@ -1883,35 +1850,35 @@ describe('MatDialog', () => { expect(button.getAttribute('type')).toBe('submit'); }); - it('should return the [mat-dialog-close] result when clicking the close button', async () => { + it('should return the [mat-dialog-close] result when clicking the close button', fakeAsync(() => { let afterCloseCallback = jasmine.createSpy('afterClose callback'); dialogRef.afterClosed().subscribe(afterCloseCallback); (overlayContainerElement.querySelector('button.close-with-true') as HTMLElement).click(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(afterCloseCallback).toHaveBeenCalledWith(true); - }); + })); - it('should set the aria-labelledby attribute to the id of the title', async () => { + it('should set the aria-labelledby attribute to the id of the title', fakeAsync(() => { let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; let container = overlayContainerElement.querySelector('mat-dialog-container')!; - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(title.id).withContext('Expected title element to have an id.').toBeTruthy(); expect(container.getAttribute('aria-labelledby')) .withContext('Expected the aria-labelledby to match the title id.') .toBe(title.id); - }); + })); - it('should update the aria-labelledby attribute if two titles are swapped', async () => { + it('should update the aria-labelledby attribute if two titles are swapped', fakeAsync(() => { const container = overlayContainerElement.querySelector('mat-dialog-container')!; let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); const previousId = title.id; @@ -1921,22 +1888,22 @@ describe('MatDialog', () => { hostInstance.shownTitle = 'second'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); title = overlayContainerElement.querySelector('[mat-dialog-title]')!; expect(title.id).toBeTruthy(); expect(title.id).not.toBe(previousId); expect(container.getAttribute('aria-labelledby')).toBe(title.id); - }); + })); - it('should update the aria-labelledby attribute if multiple titles are present and one is removed', async () => { + it('should update the aria-labelledby attribute if multiple titles are present and one is removed', fakeAsync(() => { const container = overlayContainerElement.querySelector('mat-dialog-container')!; hostInstance.shownTitle = 'all'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); const titles = overlayContainerElement.querySelectorAll('[mat-dialog-title]'); @@ -1947,11 +1914,11 @@ describe('MatDialog', () => { hostInstance.shownTitle = 'second'; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(container.getAttribute('aria-labelledby')).toBe(titles[1].id); - }); + })); it('should add correct css class according to given [align] input in [mat-dialog-actions]', () => { let actions = overlayContainerElement.querySelector('mat-dialog-actions')!; @@ -1981,39 +1948,39 @@ describe('MatDialog', () => { it( 'should not set the aria-labelledby automatically if it has an aria-label ' + 'and an aria-labelledby', - async () => { + fakeAsync(() => { dialog.open(ContentElementDialog, { ariaLabel: 'Hello there', ariaLabelledBy: 'Labelled By', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.hasAttribute('aria-labelledby')).toBe(false); - }, + }), ); it( 'should set the aria-labelledby attribute to the config provided aria-labelledby ' + 'instead of the mat-dialog-title id', - async () => { + fakeAsync(() => { dialog.open(ContentElementDialog, { ariaLabelledBy: 'Labelled By', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); let title = overlayContainerElement.querySelector('[mat-dialog-title]')!; let container = overlayContainerElement.querySelector('mat-dialog-container')!; - await viewContainerFixture.whenStable(); + flush(); viewContainerFixture.detectChanges(); expect(title.id).withContext('Expected title element to have an id.').toBeTruthy(); expect(container.getAttribute('aria-labelledby')).toBe('Labelled By'); - }, + }), ); }); @@ -2026,24 +1993,24 @@ describe('MatDialog', () => { expect(container.getAttribute('aria-label')).toBe('Hello there'); }); - it('should not set the aria-labelledby automatically if it has an aria-label', async () => { + it('should not set the aria-labelledby automatically if it has an aria-label', fakeAsync(() => { dialog.open(ContentElementDialog, { ariaLabel: 'Hello there', viewContainerRef: testViewContainerRef, }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + tick(); viewContainerFixture.detectChanges(); const container = overlayContainerElement.querySelector('mat-dialog-container')!; expect(container.hasAttribute('aria-labelledby')).toBe(false); - }); + })); }); - it('should dispose backdrop if containing dialog view is destroyed', async () => { + it('should dispose backdrop if containing dialog view is destroyed', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeDefined(); @@ -2051,10 +2018,10 @@ describe('MatDialog', () => { viewContainerFixture.componentInstance.showChildView = false; viewContainerFixture.changeDetectorRef.markForCheck(); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); + flush(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBe(null); - }); + })); }); describe('MatDialog with a parent MatDialog', () => { @@ -2063,7 +2030,7 @@ describe('MatDialog with a parent MatDialog', () => { let overlayContainerElement: HTMLElement; let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ { @@ -2082,17 +2049,16 @@ describe('MatDialog with a parent MatDialog', () => { fixture = TestBed.createComponent(ComponentThatProvidesMatDialog); childDialog = fixture.componentInstance.dialog; fixture.detectChanges(); - }); + })); afterEach(() => { overlayContainerElement.innerHTML = ''; }); - it('should close dialogs opened by a parent when calling closeAll on a child MatDialog', async () => { + it('should close dialogs opened by a parent when calling closeAll on a child MatDialog', fakeAsync(() => { parentDialog.open(PizzaMsg); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') @@ -2100,19 +2066,16 @@ describe('MatDialog with a parent MatDialog', () => { childDialog.closeAll(); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on child MatDialog to close dialog opened by parent') .toBe(''); - }); + })); - it('should close dialogs opened by a child when calling closeAll on a parent MatDialog', async () => { + it('should close dialogs opened by a child when calling closeAll on a parent MatDialog', fakeAsync(() => { childDialog.open(PizzaMsg); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') @@ -2120,33 +2083,27 @@ describe('MatDialog with a parent MatDialog', () => { parentDialog.closeAll(); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent!.trim()) .withContext('Expected closeAll on parent MatDialog to close dialog opened by child') .toBe(''); - }); + })); - it('should close the top dialog via the escape key', async () => { + it('should close the top dialog via the escape key', fakeAsync(() => { childDialog.open(PizzaMsg); - fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull(); - }); + })); - it('should not close the parent dialogs when a child is destroyed', async () => { + it('should not close the parent dialogs when a child is destroyed', fakeAsync(() => { parentDialog.open(PizzaMsg); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') @@ -2154,13 +2111,12 @@ describe('MatDialog with a parent MatDialog', () => { childDialog.ngOnDestroy(); fixture.detectChanges(); - await wait(0); - fixture.detectChanges(); + flush(); expect(overlayContainerElement.textContent) .withContext('Expected a dialog to be opened') .toContain('Pizza'); - }); + })); }); describe('MatDialog with default options', () => { @@ -2170,7 +2126,7 @@ describe('MatDialog with default options', () => { let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { const defaultConfig = { hasBackdrop: false, disableClose: true, @@ -2195,7 +2151,7 @@ describe('MatDialog with default options', () => { viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; - }); + })); it('should use the provided defaults', () => { dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); @@ -2218,7 +2174,7 @@ describe('MatDialog with default options', () => { expect(overlayPane.style.maxHeight).toBe('150px'); }); - it('should be overridable by open() options', async () => { + it('should be overridable by open() options', fakeAsync(() => { dialog.open(PizzaMsg, { hasBackdrop: true, disableClose: false, @@ -2226,17 +2182,15 @@ describe('MatDialog with default options', () => { }); viewContainerFixture.detectChanges(); - await viewContainerFixture.whenStable(); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); viewContainerFixture.detectChanges(); - await wait(0); - viewContainerFixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy(); - }); + })); }); describe('MatDialog with animations enabled', () => { @@ -2245,14 +2199,14 @@ describe('MatDialog with animations enabled', () => { let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { dialog = TestBed.inject(MatDialog); viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; - }); + })); - it('should emit when dialog opening animation is complete', async () => { + it('should emit when dialog opening animation is complete', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const spy = jasmine.createSpy('afterOpen spy'); @@ -2263,12 +2217,13 @@ describe('MatDialog with animations enabled', () => { // callback should not be called before animation is complete expect(spy).not.toHaveBeenCalled(); - await wait(OPEN_ANIMATION_DURATION); + tick(OPEN_ANIMATION_DURATION); + flush(); expect(spy).toHaveBeenCalled(); - }); + })); - it('return the current state of the dialog', async () => { + it('should return the current state of the dialog', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); expect(dialogRef.getState()).toBe(MatDialogState.OPEN); @@ -2280,25 +2235,24 @@ describe('MatDialog with animations enabled', () => { // Ensure that the closing state is still set if half of the animation has // passed by. The dialog state should be only set to `closed` when the dialog // finished the close animation. - await wait(CLOSE_ANIMATION_DURATION / 2); + tick(CLOSE_ANIMATION_DURATION / 2); expect(dialogRef.getState()).toBe(MatDialogState.CLOSING); - // Flush the remaining duration of the closing animation. We wait for the remaining duration - // to avoid pending timer failures. - await wait(CLOSE_ANIMATION_DURATION); - + // Flush the remaining duration of the closing animation. We flush all other remaining + // tasks (e.g. the fallback close timeout) to avoid fakeAsync pending timer failures. + flush(); expect(dialogRef.getState()).toBe(MatDialogState.CLOSED); - }); + })); }); describe('MatDialog with explicit injector provided', () => { let overlayContainerElement: HTMLElement; let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); fixture = TestBed.createComponent(ModuleBoundDialogParentComponent); - }); + })); it('should use the standalone injector and render the dialog successfully', () => { fixture.componentInstance.openDialog(); @@ -2310,10 +2264,6 @@ describe('MatDialog with explicit injector provided', () => { }); }); -function wait(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - @Directive({ selector: 'dir-with-view-container', }) @@ -2322,6 +2272,7 @@ class DirectiveWithViewContainer { } @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, template: 'hello', }) class ComponentWithOnPushViewContainer { @@ -2332,7 +2283,6 @@ class ComponentWithOnPushViewContainer { selector: 'arbitrary-component', template: `@if (showChildView) {}`, imports: [DirectiveWithViewContainer], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithChildViewContainer { showChildView = true; @@ -2348,7 +2298,6 @@ class ComponentWithChildViewContainer { selector: 'arbitrary-component-with-template-ref', template: ` Cheese {{localValue}} {{data?.value}}{{setDialogRef(dialogRef)}}`, - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithTemplateRef { localValue!: string; @@ -2365,10 +2314,8 @@ class ComponentWithTemplateRef { /** Simple component for testing ComponentPortal. */ @Component({ template: '

    Pizza

    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg { - @Input() flavor = 'unknown'; dialogRef = inject>(MatDialogRef); dialogInjector = inject(Injector); directionality = inject(Directionality); @@ -2398,7 +2345,6 @@ class PizzaMsg { `, imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose], - changeDetection: ChangeDetectionStrategy.Eager, }) class ContentElementDialog { shownTitle: 'first' | 'second' | 'third' | 'all' = 'first'; @@ -2434,7 +2380,6 @@ class ContentElementDialog { `, imports: [MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithContentElementTemplateRef { @ViewChild(TemplateRef) templateRef!: TemplateRef; @@ -2449,7 +2394,6 @@ class ComponentWithContentElementTemplateRef { @Component({ template: '', providers: [MatDialog], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentThatProvidesMatDialog { dialog = inject(MatDialog); @@ -2458,7 +2402,6 @@ class ComponentThatProvidesMatDialog { /** Simple component for testing ComponentPortal. */ @Component({ template: '', - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithInjectedData { data = inject(MAT_DIALOG_DATA); @@ -2466,27 +2409,24 @@ class DialogWithInjectedData { @Component({ template: '

    Pasta

    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogWithoutFocusableElements {} @Component({ template: ``, encapsulation: ViewEncapsulation.ShadowDom, - changeDetection: ChangeDetectionStrategy.Eager, }) class ShadowDomComponent {} @Component({ template: '', - changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogParentComponent { private _injector = inject(Injector); private _dialog = inject(MatDialog); openDialog(): void { - const ngModuleRef = createNgModule( + const ngModuleRef = createNgModuleRef( ModuleBoundDialogModule, /* parentInjector */ this._injector, ); @@ -2495,7 +2435,7 @@ class ModuleBoundDialogParentComponent { } } -@Service({autoProvided: false}) +@Injectable() class ModuleBoundDialogService { name = 'Pasta'; } @@ -2503,14 +2443,12 @@ class ModuleBoundDialogService { @Component({ template: '', imports: [forwardRef(() => ModuleBoundDialogChildComponent)], - changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogComponent {} @Component({ selector: 'module-bound-dialog-child-component', template: '

    {{service.name}}

    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class ModuleBoundDialogChildComponent { service = inject(ModuleBoundDialogService); @@ -2525,6 +2463,7 @@ class ModuleBoundDialogModule {} @Component({ template: `{{message | async}}`, imports: [AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) class DialogWithAfterOpenSubscription { dialogRef = inject(MatDialogRef); diff --git a/src/material/dialog/dialog.ts b/src/material/dialog/dialog.ts index 3e14184ebb3f..1452db39b75e 100644 --- a/src/material/dialog/dialog.ts +++ b/src/material/dialog/dialog.ts @@ -14,7 +14,7 @@ import { } from '@angular/cdk/overlay'; import { ComponentRef, - Service, + Injectable, InjectionToken, Injector, OnDestroy, @@ -54,7 +54,7 @@ export const MAT_DIALOG_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrateg /** * Service to open Material Design modal dialogs. */ -@Service() +@Injectable({providedIn: 'root'}) export class MatDialog implements OnDestroy { private _defaultOptions = inject(MAT_DIALOG_DEFAULT_OPTIONS, {optional: true}); private _scrollStrategy = inject(MAT_DIALOG_SCROLL_STRATEGY); @@ -98,6 +98,8 @@ export class MatDialog implements OnDestroy { : this._getAfterAllClosed().pipe(startWith(undefined)), ) as Observable; + constructor(...args: unknown[]); + constructor() { this._dialogRefConstructor = MatDialogRef; this._dialogContainerType = MatDialogContainer; diff --git a/src/material/dialog/dialog.zone.spec.ts b/src/material/dialog/dialog.zone.spec.ts index b6ab3d72d773..4c96983e9e33 100644 --- a/src/material/dialog/dialog.zone.spec.ts +++ b/src/material/dialog/dialog.zone.spec.ts @@ -10,14 +10,13 @@ import { ViewContainerRef, provideZoneChangeDetection, inject, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing'; import {MatDialog, MatDialogRef} from '../dialog'; import {Subject} from 'rxjs'; import {MATERIAL_ANIMATIONS} from '../core'; -describe('MatDialog with Zone', () => { +describe('MatDialog', () => { let dialog: MatDialog; let zone: NgZone; let scrolledSubject = new Subject(); @@ -25,7 +24,7 @@ describe('MatDialog with Zone', () => { let testViewContainerRef: ViewContainerRef; let viewContainerFixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ provideZoneChangeDetection(), @@ -47,9 +46,9 @@ describe('MatDialog with Zone', () => { viewContainerFixture = TestBed.createComponent(ComponentWithChildViewContainer); viewContainerFixture.detectChanges(); testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; - }); + })); - it('should invoke the afterClosed callback inside the NgZone', async () => { + it('should invoke the afterClosed callback inside the NgZone', fakeAsync(() => { const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef}); const afterCloseCallback = jasmine.createSpy('afterClose callback'); @@ -59,11 +58,11 @@ describe('MatDialog with Zone', () => { zone.run(() => { dialogRef.close(); viewContainerFixture.detectChanges(); + flush(); }); - await viewContainerFixture.whenStable(); expect(afterCloseCallback).toHaveBeenCalledWith(true); - }); + })); }); @Directive({ @@ -77,7 +76,6 @@ class DirectiveWithViewContainer { selector: 'arbitrary-component', template: `@if (showChildView) {}`, imports: [DirectiveWithViewContainer], - changeDetection: ChangeDetectionStrategy.Eager, }) class ComponentWithChildViewContainer { showChildView = true; @@ -92,7 +90,6 @@ class ComponentWithChildViewContainer { /** Simple component for testing ComponentPortal. */ @Component({ template: '

    Pizza

    ', - changeDetection: ChangeDetectionStrategy.Eager, }) class PizzaMsg { dialogRef = inject>(MatDialogRef); diff --git a/src/material/dialog/testing/dialog-harness.spec.ts b/src/material/dialog/testing/dialog-harness.spec.ts index ecef8c96ec6b..3c8d43973bff 100644 --- a/src/material/dialog/testing/dialog-harness.spec.ts +++ b/src/material/dialog/testing/dialog-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, TemplateRef, ViewChild, inject, ChangeDetectionStrategy} from '@angular/core'; +import {Component, TemplateRef, ViewChild, inject} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -123,7 +123,6 @@ describe('MatDialogHarness', () => { `, imports: [MatDialogTitle, MatDialogContent, MatDialogActions], - changeDetection: ChangeDetectionStrategy.Eager, }) class DialogHarnessTest { readonly dialog = inject(MatDialog); diff --git a/src/material/dialog/testing/dialog-opener.spec.ts b/src/material/dialog/testing/dialog-opener.spec.ts index d7a77fde86a6..c4c6a6bcada3 100644 --- a/src/material/dialog/testing/dialog-opener.spec.ts +++ b/src/material/dialog/testing/dialog-opener.spec.ts @@ -1,17 +1,15 @@ -import {Component, inject, ChangeDetectionStrategy} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, inject} from '@angular/core'; +import {TestBed, fakeAsync, flush} from '@angular/core/testing'; import {MAT_DIALOG_DATA, MatDialogRef, MatDialogState} from '../../dialog'; import {MatTestDialogOpener} from './dialog-opener'; describe('MatTestDialogOpener', () => { - it('should open a dialog when created', async () => { + it('should open a dialog when created', fakeAsync(() => { const fixture = TestBed.createComponent(MatTestDialogOpener.withComponent(ExampleComponent)); - fixture.detectChanges(); - await fixture.whenStable(); - + flush(); expect(fixture.componentInstance.dialogRef.getState()).toBe(MatDialogState.OPEN); expect(document.querySelector('mat-dialog-container')).toBeTruthy(); - }); + })); it('should throw an error if no dialog component is provided', () => { expect(() => TestBed.createComponent(MatTestDialogOpener)).toThrow( @@ -29,7 +27,7 @@ describe('MatTestDialogOpener', () => { expect(dialogContainer!.innerHTML).toContain('Data: test'); }); - it('should get closed result data', async () => { + it('should get closed result data', fakeAsync(() => { const config = {data: 'test'}; const fixture = TestBed.createComponent( MatTestDialogOpener.withComponent( @@ -37,12 +35,12 @@ describe('MatTestDialogOpener', () => { config, ), ); + flush(); const closeButton = document.querySelector('#close-btn') as HTMLElement; closeButton.click(); - await new Promise(resolve => setTimeout(resolve, 100)); - + flush(); expect(fixture.componentInstance.closedResult).toEqual({reason: 'closed'}); - }); + })); }); interface ExampleDialogResult { @@ -55,7 +53,6 @@ interface ExampleDialogResult { Data: {{data}} `, - changeDetection: ChangeDetectionStrategy.Eager, }) class ExampleComponent { dialogRef = inject>(MatDialogRef); diff --git a/src/material/dialog/testing/dialog-opener.ts b/src/material/dialog/testing/dialog-opener.ts index 43e55b5c713a..6f2fdf81a47c 100644 --- a/src/material/dialog/testing/dialog-opener.ts +++ b/src/material/dialog/testing/dialog-opener.ts @@ -7,7 +7,15 @@ */ import {ComponentType} from '@angular/cdk/overlay'; -import {Component, NgModule, NgZone, OnDestroy, ViewEncapsulation, inject} from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + NgModule, + NgZone, + OnDestroy, + ViewEncapsulation, + inject, +} from '@angular/core'; import {MatDialog, MatDialogConfig, MatDialogModule, MatDialogRef} from '../../dialog'; import {Subscription} from 'rxjs'; import {AnimationsConfig, MATERIAL_ANIMATIONS} from '@angular/material/core'; @@ -16,6 +24,7 @@ import {AnimationsConfig, MATERIAL_ANIMATIONS} from '@angular/material/core'; @Component({ selector: 'mat-test-dialog-opener', template: '', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MatTestDialogOpener implements OnDestroy { @@ -47,6 +56,8 @@ export class MatTestDialogOpener implements OnDestroy return MatTestDialogOpener as ComponentType>; } + constructor(...args: unknown[]); + constructor() { if (!MatTestDialogOpener.component) { throw new Error(`MatTestDialogOpener does not have a component provided.`); diff --git a/src/material/divider/divider.spec.ts b/src/material/divider/divider.spec.ts index 8f7f31923b27..b64417fb1145 100644 --- a/src/material/divider/divider.spec.ts +++ b/src/material/divider/divider.spec.ts @@ -1,5 +1,5 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {By} from '@angular/platform-browser'; import {MatDividerModule} from './divider-module'; @@ -60,7 +60,6 @@ describe('MatDivider', () => { @Component({ template: ``, imports: [MatDividerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatDividerTestComponent { vertical = false; diff --git a/src/material/divider/divider.ts b/src/material/divider/divider.ts index 316a24e472d7..b1ccf98cf769 100644 --- a/src/material/divider/divider.ts +++ b/src/material/divider/divider.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, Input, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; @Component({ @@ -22,6 +22,7 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; template: '', styleUrl: 'divider.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatDivider { /** Whether the divider is vertically aligned. */ diff --git a/src/material/divider/testing/divider-harness.spec.ts b/src/material/divider/testing/divider-harness.spec.ts index 747b4a3eda6e..cb691cd1cd3a 100644 --- a/src/material/divider/testing/divider-harness.spec.ts +++ b/src/material/divider/testing/divider-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -39,6 +39,5 @@ describe('MatLegacyButtonHarness', () => { `, imports: [MatDividerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DividerHarnessTest {} diff --git a/src/material/expansion/accordion.spec.ts b/src/material/expansion/accordion.spec.ts index d1e495268b18..c6e19f79c1f3 100644 --- a/src/material/expansion/accordion.spec.ts +++ b/src/material/expansion/accordion.spec.ts @@ -5,13 +5,7 @@ import { dispatchEvent, dispatchKeyboardEvent, } from '@angular/cdk/testing/private'; -import { - Component, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import { @@ -313,7 +307,6 @@ describe('MatAccordion', () => { } `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SetOfItems { @ViewChild(MatAccordion) accordion!: MatAccordion; @@ -342,7 +335,6 @@ class SetOfItems { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedAccordions { @ViewChildren(MatExpansionPanelHeader) headers!: QueryList; @@ -362,7 +354,6 @@ class NestedAccordions { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedPanel { @ViewChild('outerPanel') outerPanel!: MatExpansionPanel; @@ -378,7 +369,6 @@ class NestedPanel { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AccordionWithHideToggle { hideToggle = false; @@ -393,7 +383,6 @@ class AccordionWithHideToggle { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class AccordionWithTogglePosition { togglePosition: MatAccordionTogglePosition = 'after'; diff --git a/src/material/expansion/expansion-panel-content.ts b/src/material/expansion/expansion-panel-content.ts index aadf20ef1963..e16915cd8a2f 100644 --- a/src/material/expansion/expansion-panel-content.ts +++ b/src/material/expansion/expansion-panel-content.ts @@ -19,4 +19,7 @@ import {MAT_EXPANSION_PANEL, MatExpansionPanelBase} from './expansion-panel-base export class MatExpansionPanelContent { _template = inject>(TemplateRef); _expansionPanel = inject(MAT_EXPANSION_PANEL, {optional: true}); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/material/expansion/expansion-panel-header.scss b/src/material/expansion/expansion-panel-header.scss index 31935508323f..bbf6ef69f864 100644 --- a/src/material/expansion/expansion-panel-header.scss +++ b/src/material/expansion/expansion-panel-header.scss @@ -11,7 +11,6 @@ $fallbacks: m3-expansion.get-tokens(); align-items: center; padding: 0 24px; border-radius: inherit; - outline: 0; .mat-expansion-panel-animations-enabled & { transition: height expansion-variables.$header-transition; @@ -61,6 +60,11 @@ $fallbacks: m3-expansion.get-tokens(); transition: none; } + &:focus, + &:hover { + outline: none; + } + &.mat-expanded:focus, &.mat-expanded:hover { background: inherit; diff --git a/src/material/expansion/expansion-panel-header.ts b/src/material/expansion/expansion-panel-header.ts index 1c7ee65ccff7..548d013c3d93 100644 --- a/src/material/expansion/expansion-panel-header.ts +++ b/src/material/expansion/expansion-panel-header.ts @@ -10,6 +10,7 @@ import {FocusableOption, FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {ENTER, hasModifierKey, SPACE} from '@angular/cdk/keycodes'; import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, @@ -40,6 +41,7 @@ import {_StructuralStylesLoader} from '../core'; styleUrl: 'expansion-panel-header.css', templateUrl: 'expansion-panel-header.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { 'class': 'mat-expansion-panel-header mat-focus-indicator', 'role': 'button', @@ -64,6 +66,8 @@ export class MatExpansionPanelHeader implements AfterViewInit, OnDestroy, Focusa private _parentChangeSubscription = Subscription.EMPTY; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); const panel = this.panel; diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index b89e4c17c8bd..abff26d770ec 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -12,6 +12,7 @@ import {CdkPortalOutlet, TemplatePortal} from '@angular/cdk/portal'; import { AfterContentInit, + ChangeDetectionStrategy, Component, ContentChild, Directive, @@ -75,6 +76,7 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS = exportAs: 'matExpansionPanel', templateUrl: 'expansion-panel.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [ // Provide MatAccordion as undefined to prevent nested expansion panels from registering // to the same accordion. @@ -127,7 +129,7 @@ export class MatExpansionPanel @Output() readonly afterCollapse = new EventEmitter(); /** Stream that emits for changes in `@Input` properties. */ - readonly _inputChanges = new Subject>(); + readonly _inputChanges = new Subject(); /** Optionally defined accordion the expansion panel belongs to. */ override accordion = inject(MAT_ACCORDION, {optional: true, skipSelf: true})!; @@ -148,6 +150,8 @@ export class MatExpansionPanel /** ID for the associated header element. Used for a11y labelling. */ _headerId: string = inject(_IdGenerator).getId('mat-expansion-panel-header-'); + constructor(...args: unknown[]); + constructor() { super(); @@ -208,7 +212,7 @@ export class MatExpansionPanel this._setupAnimationEvents(); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { this._inputChanges.next(changes); } diff --git a/src/material/expansion/expansion.spec.ts b/src/material/expansion/expansion.spec.ts index cefaf9a5ee94..acda71fd8f88 100644 --- a/src/material/expansion/expansion.spec.ts +++ b/src/material/expansion/expansion.spec.ts @@ -4,7 +4,7 @@ import { dispatchEvent, dispatchKeyboardEvent, } from '@angular/cdk/testing/private'; -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import { ComponentFixture, TestBed, @@ -556,7 +556,6 @@ describe('MatExpansionPanel', () => { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelWithContent { expanded = false; @@ -578,7 +577,6 @@ class PanelWithContent { } `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelWithContentInNgIf { expansionShown = true; @@ -595,7 +593,6 @@ class PanelWithContentInNgIf { corrupti maxime voluptate accusamus impedit atque incidunt pariatur. `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelWithCustomMargin { expanded = false; @@ -612,7 +609,6 @@ class PanelWithCustomMargin { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class LazyPanelWithContent { expanded = false; @@ -628,7 +624,6 @@ class LazyPanelWithContent { `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class LazyPanelOpenOnLoad {} @@ -638,7 +633,6 @@ class LazyPanelOpenOnLoad {} Panel Title `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelWithTwoWayBinding { expanded = false; @@ -650,7 +644,6 @@ class PanelWithTwoWayBinding { Panel Title `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PanelWithHeaderTabindex {} @@ -665,7 +658,6 @@ class PanelWithHeaderTabindex {} `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedLazyPanelWithContent { parentExpanded = false; diff --git a/src/material/expansion/testing/expansion-harness.spec.ts b/src/material/expansion/testing/expansion-harness.spec.ts index 4bb68c6ca567..10e1638d8320 100644 --- a/src/material/expansion/testing/expansion-harness.spec.ts +++ b/src/material/expansion/testing/expansion-harness.spec.ts @@ -1,6 +1,6 @@ import {ComponentHarness, HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {MatAccordionTogglePosition, MatExpansionModule} from '../../expansion'; import {MatAccordionHarness} from './accordion-harness'; @@ -324,7 +324,6 @@ function getActiveElementTag() {
    Outside of expansion panel
    `, imports: [MatExpansionModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ExpansionHarnessTestComponent { panel1Expanded = false; diff --git a/src/material/form-field/directives/error.ts b/src/material/form-field/directives/error.ts index ab56e375f76c..f66204c1f0f7 100644 --- a/src/material/form-field/directives/error.ts +++ b/src/material/form-field/directives/error.ts @@ -6,7 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Directive, InjectionToken, Input, inject} from '@angular/core'; +import { + Directive, + ElementRef, + HostAttributeToken, + InjectionToken, + Input, + inject, +} from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; /** @@ -21,10 +28,24 @@ export const MAT_ERROR = new InjectionToken('MatError'); selector: 'mat-error, [matError]', host: { 'class': 'mat-mdc-form-field-error mat-mdc-form-field-bottom-align', + 'aria-atomic': 'true', '[id]': 'id', }, providers: [{provide: MAT_ERROR, useExisting: MatError}], }) export class MatError { @Input() id: string = inject(_IdGenerator).getId('mat-mdc-error-'); + + constructor(...args: unknown[]); + + constructor() { + const ariaLive = inject(new HostAttributeToken('aria-live'), {optional: true}); + + // If no aria-live value is set add 'polite' as a default. This is preferred over setting + // role='alert' so that screen readers do not interrupt the current task to read this aloud. + if (!ariaLive) { + const elementRef = inject(ElementRef); + elementRef.nativeElement.setAttribute('aria-live', 'polite'); + } + } } diff --git a/src/material/form-field/directives/floating-label.ts b/src/material/form-field/directives/floating-label.ts index 7f5f9bd2f2ec..0af52fbb7058 100644 --- a/src/material/form-field/directives/floating-label.ts +++ b/src/material/form-field/directives/floating-label.ts @@ -89,6 +89,9 @@ export class MatFormFieldFloatingLabel implements OnDestroy { /** The current resize event subscription. */ private _resizeSubscription = new Subscription(); + constructor(...args: unknown[]); + constructor() {} + ngOnDestroy() { this._resizeSubscription.unsubscribe(); } diff --git a/src/material/form-field/directives/line-ripple.ts b/src/material/form-field/directives/line-ripple.ts index 4197ed37f70f..f334b3314dd4 100644 --- a/src/material/form-field/directives/line-ripple.ts +++ b/src/material/form-field/directives/line-ripple.ts @@ -32,6 +32,8 @@ export class MatFormFieldLineRipple implements OnDestroy { private _elementRef = inject>(ElementRef); private _cleanupTransitionEnd!: () => void; + constructor(...args: unknown[]); + constructor() { const ngZone = inject(NgZone); const renderer = inject(Renderer2); diff --git a/src/material/form-field/directives/notched-outline.ts b/src/material/form-field/directives/notched-outline.ts index 216e2b5eb540..e485293a01b5 100644 --- a/src/material/form-field/directives/notched-outline.ts +++ b/src/material/form-field/directives/notched-outline.ts @@ -8,6 +8,7 @@ import { AfterViewInit, + ChangeDetectionStrategy, Component, ElementRef, Input, @@ -32,6 +33,7 @@ import { // a host binding in order to ensure that the notched-outline renders correctly on the server. '[class.mdc-notched-outline--notched]': 'open', }, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MatFormFieldNotchedOutline implements AfterViewInit { diff --git a/src/material/form-field/form-field.html b/src/material/form-field/form-field.html index b5e38afad4fb..73f3ea5847e9 100644 --- a/src/material/form-field/form-field.html +++ b/src/material/form-field/form-field.html @@ -95,7 +95,7 @@ }
    -
    diff --git a/src/material/form-field/form-field.md b/src/material/form-field/form-field.md index bdcf41bcd117..87e25dcec8bc 100644 --- a/src/material/form-field/form-field.md +++ b/src/material/form-field/form-field.md @@ -11,9 +11,7 @@ The following Angular Material components are designed to work inside a `` & ` `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputTextareaWithBindings { rows: number = 4; @@ -2034,7 +2016,6 @@ class MatInputTextareaWithBindings { @Component({ template: ``, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputMissingMatInputTestController {} @@ -2051,7 +2032,6 @@ class MatInputMissingMatInputTestController {} `, imports: [MatInputModule, ReactiveFormsModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithFormErrorMessages { @ViewChild('form') form!: NgForm; @@ -2075,7 +2055,6 @@ class MatInputWithFormErrorMessages { `, imports: [MatInputModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InputInFormGroup { formGroup = new FormGroup({ @@ -2100,7 +2079,6 @@ class InputInFormGroup { `, imports: [MatInputModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithFormGroupErrorMessages { @ViewChild(FormGroupDirective) formGroupDirective!: FormGroupDirective; @@ -2120,7 +2098,6 @@ class MatInputWithFormGroupErrorMessages { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithPrefixAndSuffix {} @@ -2133,13 +2110,13 @@ class MatInputWithPrefixAndSuffix {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithNgIf { renderInput = true; } @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, template: ` Label @@ -2160,7 +2137,6 @@ class MatInputOnPush { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithLabel {} @@ -2172,7 +2148,6 @@ class MatInputWithLabel {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithLabelAndPlaceholder { floatLabel!: FloatLabelType; @@ -2187,7 +2162,6 @@ class MatInputWithLabelAndPlaceholder { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithAppearance { @ViewChild(MatFormField) formField!: MatFormField; @@ -2202,7 +2176,6 @@ class MatInputWithAppearance { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithSubscriptSizing { @ViewChild(MatFormField) formField!: MatFormField; @@ -2216,7 +2189,6 @@ class MatInputWithSubscriptSizing { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithoutPlaceholder {} @@ -2232,7 +2204,6 @@ class MatInputWithoutPlaceholder {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSelect { disabled: boolean = false; @@ -2251,7 +2222,6 @@ class MatInputSelect { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSelectWithNoLabelNoValue {} @@ -2267,7 +2237,6 @@ class MatInputSelectWithNoLabelNoValue {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSelectWithLabel {} @@ -2283,7 +2252,6 @@ class MatInputSelectWithLabel {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSelectWithInnerHtml {} @@ -2311,7 +2279,6 @@ class CustomMatInputAccessor { `, imports: [MatInputModule, CustomMatInputAccessor], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithCustomAccessor {} @@ -2322,7 +2289,6 @@ class MatInputWithCustomAccessor {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSelectWithoutOptions {} @@ -2332,7 +2298,6 @@ class MatInputSelectWithoutOptions {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithColor { color!: ThemePalette; @@ -2347,7 +2312,6 @@ class MatInputWithColor { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputInsideOutsideFormField {} @@ -2358,7 +2322,6 @@ class MatInputInsideOutsideFormField {} `, imports: [MatInputModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithRequiredFormControl { formControl = new FormControl('', [Validators.required]); @@ -2371,7 +2334,6 @@ class MatInputWithRequiredFormControl { `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputSimple {} @@ -2386,7 +2348,6 @@ class MatInputSimple {} `, imports: [MatInputModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InputWithNgContainerPrefixAndSuffix {} @@ -2397,7 +2358,6 @@ class InputWithNgContainerPrefixAndSuffix {} `, imports: [MatInputModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatInputWithRequiredAssignableFormControl { formControl = new FormControl('', [Validators.required]); diff --git a/src/material/input/input.ts b/src/material/input/input.ts index ab71331eaaf8..41f116828a17 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -28,7 +28,6 @@ import { } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms'; -import {FORM_FIELD} from '@angular/forms/signals'; import {ErrorStateMatcher, _ErrorStateTracker} from '../core'; import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '../form-field'; import {Subject} from 'rxjs'; @@ -295,12 +294,13 @@ export class MatInput 'week', ].filter(t => getSupportedInputTypes().has(t)); + constructor(...args: unknown[]); + constructor() { const parentForm = inject(NgForm, {optional: true}); const parentFormGroup = inject(FormGroupDirective, {optional: true}); const defaultErrorStateMatcher = inject(ErrorStateMatcher); const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true}); - const formField = inject(FORM_FIELD, {optional: true, self: true}); const element = this._elementRef.nativeElement; const nodeName = element.nodeName.toLowerCase(); @@ -333,7 +333,7 @@ export class MatInput this._errorStateTracker = new _ErrorStateTracker( defaultErrorStateMatcher, - formField || this.ngControl, + this.ngControl, parentFormGroup, parentForm, this.stateChanges, diff --git a/src/material/input/testing/input-harness.spec.ts b/src/material/input/testing/input-harness.spec.ts index 20382ca4fe29..c9825d0d5601 100644 --- a/src/material/input/testing/input-harness.spec.ts +++ b/src/material/input/testing/input-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -297,7 +297,6 @@ describe('MatInputHarness', () => { `, imports: [MatInputModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class InputHarnessTest { inputType = signal('number'); diff --git a/src/material/input/testing/native-select-harness.spec.ts b/src/material/input/testing/native-select-harness.spec.ts index 3fc7eb1569ba..37184605485e 100644 --- a/src/material/input/testing/native-select-harness.spec.ts +++ b/src/material/input/testing/native-select-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -223,7 +223,6 @@ describe('MatNativeSelectHarness', () => { `, imports: [MatInputModule, FormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectHarnessTest { favoriteFood!: string; diff --git a/src/material/list/action-list.ts b/src/material/list/action-list.ts index d8617f381b2f..5e074c0e9ce0 100644 --- a/src/material/list/action-list.ts +++ b/src/material/list/action-list.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import {MatListBase} from './list-base'; @Component({ @@ -19,6 +19,7 @@ import {MatListBase} from './list-base'; }, styleUrl: 'list.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [{provide: MatListBase, useExisting: MatActionList}], }) export class MatActionList extends MatListBase { diff --git a/src/material/list/list-base.ts b/src/material/list/list-base.ts index 7bb840b814c8..cca24c2d5dbd 100644 --- a/src/material/list/list-base.ts +++ b/src/material/list/list-base.ts @@ -177,6 +177,8 @@ export abstract class MatListItemBase implements AfterViewInit, OnDestroy, Rippl return this.disableRipple || !!this.rippleConfig.disabled; } + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); const globalRippleOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, { diff --git a/src/material/list/list-item-sections.ts b/src/material/list/list-item-sections.ts index 14bf83ba3c35..c2e77af868bb 100644 --- a/src/material/list/list-item-sections.ts +++ b/src/material/list/list-item-sections.ts @@ -21,6 +21,9 @@ import {LIST_OPTION, ListOption} from './list-option-types'; }) export class MatListItemTitle { _elementRef = inject>(ElementRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -35,6 +38,9 @@ export class MatListItemTitle { }) export class MatListItemLine { _elementRef = inject>(ElementRef); + + constructor(...args: unknown[]); + constructor() {} } /** @@ -70,6 +76,9 @@ export class MatListItemMeta {} export class _MatListItemGraphicBase { _listOption = inject(LIST_OPTION, {optional: true}); + constructor(...args: unknown[]); + constructor() {} + _isAlignedAtStart() { // By default, in all list items the graphic is aligned at start. In list options, // the graphic is only aligned at start if the checkbox/radio is at the end. diff --git a/src/material/list/list-option.ts b/src/material/list/list-option.ts index 4ee9bd60ae81..c8aa35ad7cde 100644 --- a/src/material/list/list-option.ts +++ b/src/material/list/list-option.ts @@ -9,6 +9,7 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {SelectionModel} from '@angular/cdk/collections'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, @@ -91,6 +92,7 @@ export interface SelectionList extends MatListBase { }, templateUrl: 'list-option.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [ {provide: MatListItemBase, useExisting: MatListOption}, {provide: LIST_OPTION, useExisting: MatListOption}, @@ -116,6 +118,19 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit /** Whether the label should appear before or after the checkbox/radio. Defaults to 'after' */ @Input() togglePosition: MatListOptionTogglePosition = 'after'; + /** + * Whether the label should appear before or after the checkbox/radio. Defaults to 'after' + * + * @deprecated Use `togglePosition` instead. + * @breaking-change 17.0.0 + */ + @Input() get checkboxPosition(): MatListOptionTogglePosition { + return this.togglePosition; + } + set checkboxPosition(value: MatListOptionTogglePosition) { + this.togglePosition = value; + } + /** * Theme color of the list option. This sets the color of the checkbox/radio. * This API is supported in M2 themes only, it has no effect in M3 themes. For color customization diff --git a/src/material/list/list.spec.ts b/src/material/list/list.spec.ts index 691f04e437d4..4a6f13e9f8c7 100644 --- a/src/material/list/list.spec.ts +++ b/src/material/list/list.spec.ts @@ -1,12 +1,6 @@ import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing/private'; -import { - Component, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; -import {TestBed} from '@angular/core/testing'; +import {Component, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {TestBed, fakeAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MatListItem, MatListModule} from './index'; @@ -133,7 +127,7 @@ describe('MatList', () => { expect(activeItem.getAttribute('aria-current')).toBe('page'); }); - it('should not show ripples for non-nav lists', () => { + it('should not show ripples for non-nav lists', fakeAsync(() => { const fixture = TestBed.createComponent(ListWithOneAnchorItem); fixture.detectChanges(); @@ -144,7 +138,7 @@ describe('MatList', () => { dispatchMouseEvent(item._hostElement, 'mousedown'); expect(fixture.nativeElement.querySelector('.mat-ripple-element')).toBe(null); }); - }); + })); it('should allow disabling ripples for specific nav-list items', () => { const fixture = TestBed.createComponent(NavListWithOneAnchorItem); @@ -252,7 +246,7 @@ describe('MatList', () => { expect(items.every(item => item.rippleDisabled)).toBe(true); }); - it('should disable item ripples when list ripples are disabled via the input in nav list', () => { + it('should disable item ripples when list ripples are disabled via the input in nav list', fakeAsync(() => { const fixture = TestBed.createComponent(NavListWithOneAnchorItem); fixture.detectChanges(); @@ -285,9 +279,9 @@ describe('MatList', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - }); + })); - it('should disable item ripples when list ripples are disabled via the input in an action list', () => { + it('should disable item ripples when list ripples are disabled via the input in an action list', fakeAsync(() => { const fixture = TestBed.createComponent(ActionListWithoutType); fixture.detectChanges(); @@ -320,7 +314,7 @@ describe('MatList', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - }); + })); it('should be able to disable a single list item', () => { const fixture = TestBed.createComponent(ListWithDisabledItems); @@ -417,7 +411,6 @@ class BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithOneAnchorItem extends BaseTestList { // This needs to be declared directly on the class; if declared on the BaseTestList superclass, @@ -433,7 +426,6 @@ class ListWithOneAnchorItem extends BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NavListWithOneAnchorItem extends BaseTestList { @ViewChildren(MatListItem) listItems!: QueryList; @@ -452,7 +444,6 @@ class NavListWithOneAnchorItem extends BaseTestList { } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NavListWithActivatedItem extends BaseTestList { @ViewChildren(MatListItem) listItems!: QueryList; @@ -471,7 +462,6 @@ class NavListWithActivatedItem extends BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ActionListWithoutType extends BaseTestList { @ViewChildren(MatListItem) listItems!: QueryList; @@ -487,7 +477,6 @@ class ActionListWithoutType extends BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ActionListWithType extends BaseTestList { @ViewChildren(MatListItem) listItems!: QueryList; @@ -501,7 +490,6 @@ class ActionListWithType extends BaseTestList { } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ActionListWithDisabledList extends BaseTestList { disableList = true; @@ -515,7 +503,6 @@ class ActionListWithDisabledList extends BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ActionListWithDisabledItem extends BaseTestList { @ViewChild(MatListItem) buttonItem!: MatListItem; @@ -530,7 +517,6 @@ class ActionListWithDisabledItem extends BaseTestList { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithOneItem extends BaseTestList {} @@ -546,7 +532,6 @@ class ListWithOneItem extends BaseTestList {} } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithTwoLineItem extends BaseTestList {} @@ -562,7 +547,6 @@ class ListWithTwoLineItem extends BaseTestList {} } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithThreeLineItem extends BaseTestList {} @@ -578,7 +562,6 @@ class ListWithThreeLineItem extends BaseTestList {} `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithAvatar extends BaseTestList {} @@ -593,7 +576,6 @@ class ListWithAvatar extends BaseTestList {} } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithItemWithCssClass extends BaseTestList {} @@ -611,7 +593,6 @@ class ListWithItemWithCssClass extends BaseTestList {} } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithDynamicNumberOfLines extends BaseTestList {} @@ -623,7 +604,6 @@ class ListWithDynamicNumberOfLines extends BaseTestList {} } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithMultipleItems extends BaseTestList {} @@ -635,7 +615,6 @@ class ListWithMultipleItems extends BaseTestList {} Three `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListWithDisabledItems { firstItemDisabled = false; @@ -645,6 +624,5 @@ class ListWithDisabledItems { @Component({ template: ``, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneListItem {} diff --git a/src/material/list/list.ts b/src/material/list/list.ts index 91f2544d0da4..8eec1784c9b4 100644 --- a/src/material/list/list.ts +++ b/src/material/list/list.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, Input, ContentChildren, @@ -37,6 +38,7 @@ export const MAT_LIST = new InjectionToken('MatList'); }, styleUrl: 'list.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [{provide: MatListBase, useExisting: MatList}], }) export class MatList extends MatListBase {} @@ -58,6 +60,7 @@ export class MatList extends MatListBase {} }, templateUrl: 'list-item.html', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkObserveContent], }) export class MatListItem extends MatListItemBase { diff --git a/src/material/list/nav-list.ts b/src/material/list/nav-list.ts index e3fd276fdf5c..f86700e99f1c 100644 --- a/src/material/list/nav-list.ts +++ b/src/material/list/nav-list.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, InjectionToken, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, Component, InjectionToken, ViewEncapsulation} from '@angular/core'; import {MatListBase} from './list-base'; /** @@ -26,6 +26,7 @@ export const MAT_NAV_LIST = new InjectionToken('MatNavList'); }, styleUrl: 'list.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, providers: [{provide: MatListBase, useExisting: MatNavList}], }) export class MatNavList extends MatListBase { diff --git a/src/material/list/public-api.ts b/src/material/list/public-api.ts index b7ae4be9751b..bdab0ec6ac80 100644 --- a/src/material/list/public-api.ts +++ b/src/material/list/public-api.ts @@ -17,4 +17,12 @@ export * from './list-item-sections'; export * from './tokens'; export {MatListOption} from './list-option'; -export {MatListOptionTogglePosition} from './list-option-types'; + +export { + MatListOptionTogglePosition, + /** + * @deprecated Use `MatListOptionTogglePosition` instead. + * @breaking-change 17.0.0 + */ + MatListOptionTogglePosition as MatListOptionCheckboxPosition, +} from './list-option-types'; diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts index 63a3bf13f5bd..ecc23e5470cc 100644 --- a/src/material/list/selection-list.spec.ts +++ b/src/material/list/selection-list.spec.ts @@ -13,7 +13,14 @@ import { QueryList, ViewChildren, } from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import { + ComponentFixture, + TestBed, + fakeAsync, + flush, + tick, + waitForAsync, +} from '@angular/core/testing'; import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {ThemePalette} from '../core'; import {By} from '@angular/platform-browser'; @@ -30,22 +37,18 @@ import { describe('MatSelectionList without forms', () => { const typeaheadInterval = 200; - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - describe('with list option', () => { let fixture: ComponentFixture; let listOptions: DebugElement[]; let selectionList: DebugElement; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithListOptions); fixture.detectChanges(); listOptions = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; - }); + })); function getFocusIndex() { return listOptions.findIndex(o => document.activeElement === o.nativeElement); @@ -284,7 +287,7 @@ describe('MatSelectionList without forms', () => { expect(listOptions.slice(1).every(o => o.nativeElement.tabIndex === -1)).toBe(true); }); - it('should focus the first selected option when list receives focus', () => { + it('should focus the first selected option when list receives focus', fakeAsync(() => { dispatchMouseEvent(listOptions[2].nativeElement, 'click'); fixture.detectChanges(); @@ -302,7 +305,7 @@ describe('MatSelectionList without forms', () => { fixture.detectChanges(); expect(listOptions.map(o => o.nativeElement.tabIndex)).toEqual([0, -1, -1, -1, -1]); - }); + })); it('should focus previous item when press UP ARROW', () => { listOptions[2].nativeElement.focus(); @@ -455,7 +458,7 @@ describe('MatSelectionList without forms', () => { ); }); - it('should be able to jump focus down to an item by typing', async () => { + it('should be able to jump focus down to an item by typing', fakeAsync(() => { const firstOption = listOptions[0].nativeElement; firstOption.focus(); @@ -463,42 +466,42 @@ describe('MatSelectionList without forms', () => { dispatchEvent(firstOption, createKeyboardEvent('keydown', 83, 's')); fixture.detectChanges(); - await wait(typeaheadInterval + 50); + tick(typeaheadInterval); expect(getFocusIndex()).toBe(1); dispatchEvent(firstOption, createKeyboardEvent('keydown', 68, 'd')); fixture.detectChanges(); - await wait(typeaheadInterval + 50); + tick(typeaheadInterval); expect(getFocusIndex()).toBe(4); - }); + })); - it('should be able to skip to an item by typing', async () => { + it('should be able to skip to an item by typing', fakeAsync(() => { listOptions[0].nativeElement.focus(); expect(getFocusIndex()).toBe(0); dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', D, 'd'); fixture.detectChanges(); - await wait(typeaheadInterval + 50); + tick(typeaheadInterval); expect(getFocusIndex()).toBe(4); - }); + })); // Test for "A" specifically, because it's a special case that can be used // to select all values. - it('should be able to skip to an item by typing the letter "A"', async () => { + it('should be able to skip to an item by typing the letter "A"', fakeAsync(() => { listOptions[0].nativeElement.focus(); expect(getFocusIndex()).toBe(0); dispatchKeyboardEvent(listOptions[0].nativeElement, 'keydown', A, 'a'); fixture.detectChanges(); - await wait(typeaheadInterval + 50); + tick(typeaheadInterval); expect(getFocusIndex()).toBe(3); - }); + })); - it('should not select items while using the typeahead', async () => { + it('should not select items while using the typeahead', fakeAsync(() => { const testListItem = listOptions[1].nativeElement as HTMLElement; const model = selectionList.injector.get(MatSelectionList).selectedOptions; @@ -511,17 +514,17 @@ describe('MatSelectionList without forms', () => { dispatchKeyboardEvent(testListItem, 'keydown', D, 'd'); fixture.detectChanges(); - await wait(typeaheadInterval / 2); // Wait only half the typeahead timeout. + tick(typeaheadInterval / 2); // Tick only half the typeahead timeout. dispatchKeyboardEvent(testListItem, 'keydown', SPACE); fixture.detectChanges(); - // Wait the buffer timeout again as a new key has been pressed that resets + // Tick the buffer timeout again as a new key has been pressed that resets // the buffer timeout. - await wait(typeaheadInterval + 50); + tick(typeaheadInterval); expect(getFocusIndex()).toBe(4); expect(model.isEmpty()).toBe(true); - }); + })); it('should be able to select all options', () => { const list: MatSelectionList = selectionList.componentInstance; @@ -624,7 +627,7 @@ describe('MatSelectionList without forms', () => { expect(list.options.toArray().every(option => option.selected)).toBe(true); }); - it('should disable list item ripples when the ripples on the list have been disabled', () => { + it('should disable list item ripples when the ripples on the list have been disabled', fakeAsync(() => { const rippleTarget = fixture.nativeElement.querySelector( '.mat-mdc-list-option:not(.mdc-list-item--disabled)', ); @@ -655,7 +658,7 @@ describe('MatSelectionList without forms', () => { expect(rippleTarget.querySelectorAll('.mat-ripple-element').length) .withContext('Expected no ripples after list ripples are disabled.') .toBe(0); - }); + })); it('can bind both selected and value at the same time', () => { const componentFixture = TestBed.createComponent(SelectionListWithSelectedOptionAndValue); @@ -688,12 +691,12 @@ describe('MatSelectionList without forms', () => { let listOptionElements: DebugElement[]; let selectionList: DebugElement; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - }); + })); it('should set its initial selected state in the selectedOptions', () => { let options = listOptionElements.map(optionEl => @@ -706,23 +709,23 @@ describe('MatSelectionList without forms', () => { expect(selectedOptions.isSelected(options[3])).toBeFalse(); }); - it('should focus the first selected option on first focus if an item is pre-selected', () => { + it('should focus the first selected option on first focus if an item is pre-selected', fakeAsync(() => { // MDC manages the focus through setting a `tabindex` on the designated list item. We // assert that the proper tabindex is set on the pre-selected option at index 1, and // ensure that other options are not reachable through tab. expect(listOptionElements.map(el => el.nativeElement.tabIndex)).toEqual([-1, 0, -1, -1]); - }); + })); }); describe('single-selection with list option selected', () => { let fixture: ComponentFixture; let listOptionElements: DebugElement[]; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; fixture.detectChanges(); - }); + })); it('displays radio indicators by default', () => { expect( @@ -745,19 +748,19 @@ describe('MatSelectionList without forms', () => { let fixture: ComponentFixture; let listOptionElements: DebugElement[]; - beforeEach(() => { + beforeEach(waitForAsync(() => { const matListConfig: MatListConfig = {hideSingleSelectionIndicator: true}; TestBed.configureTestingModule({ providers: [{provide: MAT_LIST_CONFIG, useValue: matListConfig}], }); - }); + })); - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SingleSelectionListWithSelectedOption); listOptionElements = fixture.debugElement.queryAll(By.directive(MatListOption))!; fixture.detectChanges(); - }); + })); it('does not display radio indicators', () => { expect(listOptionElements[0].nativeElement.querySelector('input[type="radio"]')).toBeNull(); @@ -779,7 +782,7 @@ describe('MatSelectionList without forms', () => { let listOptionEl: HTMLElement; let listOption: MatListOption; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithDisabledOption); const listOptionDebug = fixture.debugElement.query(By.directive(MatListOption))!; @@ -788,7 +791,7 @@ describe('MatSelectionList without forms', () => { listOptionEl = listOptionDebug.nativeElement; fixture.detectChanges(); - }); + })); it('should disable ripples for disabled option', () => { expect(listOption.rippleDisabled) @@ -820,12 +823,12 @@ describe('MatSelectionList without forms', () => { let listOption: DebugElement[]; let selectionList: DebugElement; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithListDisabled); listOption = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - }); + })); it('should not allow selection on disabled selection-list', () => { let testListItem = listOption[2].injector.get(MatListOption); @@ -907,10 +910,10 @@ describe('MatSelectionList without forms', () => { describe('with checkbox position after', () => { let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithCheckboxPositionAfter); fixture.detectChanges(); - }); + })); it('should be able to customize checkbox position', () => { expect(fixture.nativeElement.querySelector('.mdc-list-item__end .mdc-checkbox')) @@ -1049,14 +1052,14 @@ describe('MatSelectionList without forms', () => { let listOptions: DebugElement[]; let selectionList: DebugElement; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(SelectionListWithListOptions); fixture.componentInstance.multiple = false; fixture.changeDetectorRef.markForCheck(); listOptions = fixture.debugElement.queryAll(By.directive(MatListOption)); selectionList = fixture.debugElement.query(By.directive(MatSelectionList))!; fixture.detectChanges(); - }); + })); function getFocusIndex() { return listOptions.findIndex(o => document.activeElement === o.nativeElement); @@ -1166,18 +1169,18 @@ describe('MatSelectionList without forms', () => { ); }); - describe('with single selection and two-way binding', () => { + describe('with single selection', () => { let fixture: ComponentFixture; let optionElement: HTMLElement; let option: MatListOption; - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(ListOptionWithTwoWayBinding); fixture.detectChanges(); const optionDebug = fixture.debugElement.query(By.directive(MatListOption)); option = optionDebug.componentInstance; optionElement = optionDebug.nativeElement; - }); + })); it('should sync the value from the view to the option', () => { expect(option.selected).toBe(false); @@ -1218,7 +1221,7 @@ describe('MatSelectionList with forms', () => { .map(optionDebugEl => optionDebugEl.componentInstance); }); - it('should update the model if an option got selected programmatically', async () => { + it('should update the model if an option got selected programmatically', fakeAsync(() => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1226,14 +1229,14 @@ describe('MatSelectionList with forms', () => { listOptions[0].toggle(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - }); + })); - it('should update the model if an option got clicked', async () => { + it('should update the model if an option got clicked', fakeAsync(() => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1241,14 +1244,14 @@ describe('MatSelectionList with forms', () => { dispatchMouseEvent(listOptions[0]._hostElement, 'click'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - }); + })); - it('should update the options if a model value is set', async () => { + it('should update the options if a model value is set', fakeAsync(() => { expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected no options to be selected by default') .toBe(0); @@ -1257,19 +1260,19 @@ describe('MatSelectionList with forms', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.selectedOptions.length) .withContext('Expected first list option to be selected') .toBe(1); - }); + })); - it('should focus the first option when the list items are changed', async () => { + it('should focus the first option when the list items are changed', fakeAsync(() => { fixture.componentInstance.options = ['first option', 'second option']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); const listElements = fixture.debugElement .queryAll(By.directive(MatListOption)) @@ -1278,37 +1281,37 @@ describe('MatSelectionList with forms', () => { expect(listElements.length).toBe(2); expect(listElements[0].tabIndex).toBe(0); expect(listElements[1].tabIndex).toBe(-1); - }); + })); - it('should not mark the model as touched when the list is blurred', async () => { + it('should not mark the model as touched when the list is blurred', fakeAsync(() => { expect(ngModel.touched) .withContext('Expected the selection-list to be untouched by default.') .toBe(false); dispatchFakeEvent(selectionListDebug.nativeElement, 'blur'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(ngModel.touched) .withContext('Expected the selection-list to remain untouched.') .toBe(false); - }); + })); - it('should mark the model as touched when a list item is blurred', async () => { + it('should mark the model as touched when a list item is blurred', fakeAsync(() => { expect(ngModel.touched) .withContext('Expected the selection-list to be untouched by default.') .toBe(false); dispatchFakeEvent(fixture.nativeElement.querySelector('.mat-mdc-list-option'), 'blur'); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(ngModel.touched) .withContext('Expected the selection-list to be touched after an item is blurred.') .toBe(true); - }); + })); - it('should be pristine by default', async () => { + it('should be pristine by default', fakeAsync(() => { fixture = TestBed.createComponent(SelectionListWithModel); fixture.componentInstance.selectedOptions = ['opt2']; fixture.changeDetectorRef.markForCheck(); @@ -1323,7 +1326,7 @@ describe('MatSelectionList with forms', () => { // Flush the initial tick to ensure that every action from the ControlValueAccessor // happened before the actual test starts. - await fixture.whenStable(); + tick(); expect(ngModel.pristine) .withContext('Expected the selection-list to be pristine by default.') @@ -1332,12 +1335,12 @@ describe('MatSelectionList with forms', () => { listOptions[1].toggle(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(ngModel.pristine) .withContext('Expected the selection-list to be dirty after state change.') .toBe(false); - }); + })); it('should remove a selected option from the value on destroy', fakeAsync(() => { listOptions[1].selected = true; @@ -1354,15 +1357,15 @@ describe('MatSelectionList with forms', () => { expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); })); - it('should update the model if an option got selected via the model', async () => { + it('should update the model if an option got selected via the model', fakeAsync(() => { expect(fixture.componentInstance.selectedOptions).toEqual([]); selectionListDebug.componentInstance.selectedOptions.select(listOptions[0]); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); - }); + })); it('should not dispatch the model change event if nothing changed using selectAll', () => { expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); @@ -1378,7 +1381,7 @@ describe('MatSelectionList with forms', () => { expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1); }); - it('should not dispatch the model change event if nothing changed using deselectAll', () => { + it('should not dispatch the model change event if nothing changed using selectAll', () => { expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); selectionListDebug.componentInstance.deselectAll(); @@ -1387,11 +1390,11 @@ describe('MatSelectionList with forms', () => { expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); }); - it('should be able to programmatically set an array with duplicate values', async () => { + it('should be able to programmatically set an array with duplicate values', fakeAsync(() => { fixture.componentInstance.options = ['one', 'two', 'two', 'two', 'three']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) @@ -1400,12 +1403,12 @@ describe('MatSelectionList with forms', () => { fixture.componentInstance.selectedOptions = ['one', 'two', 'two']; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(listOptions.map(option => option.selected)).toEqual([true, true, true, false, false]); - }); + })); - it('should dispatch one change event per change when updating a single-selection list', async () => { + it('should dispatch one change event per change when updating a single-selection list', fakeAsync(() => { fixture.destroy(); fixture = TestBed.createComponent(SelectionListWithModel); fixture.componentInstance.multiple = false; @@ -1420,18 +1423,18 @@ describe('MatSelectionList with forms', () => { options[0].click(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); options[1].click(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2); expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); - }); + })); }); describe('and formControl', () => { @@ -1568,7 +1571,7 @@ describe('MatSelectionList with forms', () => { .toBe(true); }); - it('should not clear the form control when the list is destroyed', async () => { + it('should not clear the form control when the list is destroyed', fakeAsync(() => { const option = listOptions[1]; option.selected = true; @@ -1579,11 +1582,11 @@ describe('MatSelectionList with forms', () => { fixture.componentInstance.renderList = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(fixture.componentInstance.formControl.value).toEqual(['opt2']); - }); + })); it('should mark options added at a later point as selected', () => { fixture.componentInstance.formControl.setValue(['opt4']); @@ -1603,34 +1606,34 @@ describe('MatSelectionList with forms', () => { }); describe('preselected values', () => { - it('should add preselected options to the model value', async () => { + it('should add preselected options to the model value', fakeAsync(() => { const fixture = TestBed.createComponent(SelectionListWithPreselectedOption); const listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) .map(optionDebugEl => optionDebugEl.componentInstance); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(listOptions[1].selected).toBe(true); expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); - }); + })); - it('should handle preselected option both through the model and the view', async () => { + it('should handle preselected option both through the model and the view', fakeAsync(() => { const fixture = TestBed.createComponent(SelectionListWithPreselectedOptionAndModel); const listOptions = fixture.debugElement .queryAll(By.directive(MatListOption)) .map(optionDebugEl => optionDebugEl.componentInstance); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(listOptions[0].selected).toBe(true); expect(listOptions[1].selected).toBe(true); expect(fixture.componentInstance.selectedOptions).toEqual(['opt1', 'opt2']); - }); + })); - it('should show the item as selected when preselected inside OnPush parent', async () => { + it('should show the item as selected when preselected inside OnPush parent', fakeAsync(() => { const fixture = TestBed.createComponent(SelectionListWithPreselectedFormControlOnPush); fixture.detectChanges(); @@ -1640,16 +1643,16 @@ describe('MatSelectionList with forms', () => { ) as HTMLInputElement; fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(option.componentInstance.selected).toBe(true); expect(checkbox.checked).toBe(true); - }); + })); }); describe('with custom compare function', () => { - it('should use a custom comparator to determine which options are selected', async () => { + it('should use a custom comparator to determine which options are selected', fakeAsync(() => { const fixture = TestBed.createComponent(SelectionListWithCustomComparator); const testComponent = fixture.componentInstance; @@ -1662,11 +1665,11 @@ describe('MatSelectionList with forms', () => { testComponent.selectedOptions = [{id: 2, label: 'Two'}]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(testComponent.compareWith).toHaveBeenCalled(); expect(testComponent.optionInstances.toArray()[1].selected).toBe(true); - }); + })); }); }); @@ -1699,7 +1702,6 @@ describe('MatSelectionList with forms', () => { } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithListOptions { showLastOption = true; @@ -1728,7 +1730,6 @@ class SelectionListWithListOptions { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithCheckboxPositionAfter {} @@ -1749,7 +1750,6 @@ class SelectionListWithCheckboxPositionAfter {} `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithListDisabled { disabled: boolean = true; @@ -1762,7 +1762,6 @@ class SelectionListWithListDisabled { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithDisabledOption { disableItem: boolean = false; @@ -1777,7 +1776,6 @@ class SelectionListWithDisabledOption { Not selected - Item #4 `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithSelectedOption {} @@ -1788,7 +1786,6 @@ class SelectionListWithSelectedOption {} Pre-selected - Item #2 `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SingleSelectionListWithSelectedOption {} @@ -1798,7 +1795,6 @@ class SingleSelectionListWithSelectedOption {} Item `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithSelectedOptionAndValue { itemValue = 'item1'; @@ -1815,7 +1811,6 @@ class SelectionListWithSelectedOptionAndValue { } `, imports: [MatListModule, FormsModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithModel { modelChangeSpy = jasmine.createSpy('model change spy'); @@ -1838,7 +1833,6 @@ class SelectionListWithModel { } `, imports: [MatListModule, FormsModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithFormControl { formControl = new FormControl([] as string[]); @@ -1853,7 +1847,6 @@ class SelectionListWithFormControl { Option 2 `, imports: [MatListModule, FormsModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithPreselectedOption { selectedOptions!: string[]; @@ -1866,13 +1859,13 @@ class SelectionListWithPreselectedOption { Option 2 `, imports: [MatListModule, FormsModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithPreselectedOptionAndModel { selectedOptions = ['opt1']; } @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, template: ` @for (opt of opts; track opt) { @@ -1895,7 +1888,6 @@ class SelectionListWithPreselectedFormControlOnPush { } `, imports: [MatListModule, FormsModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithCustomComparator { @ViewChildren(MatListOption) optionInstances!: QueryList; @@ -1918,7 +1910,6 @@ class SelectionListWithCustomComparator { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithAvatar { togglePosition!: MatListOptionTogglePosition; @@ -1934,7 +1925,6 @@ class SelectionListWithAvatar { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithIcon { togglePosition!: MatListOptionTogglePosition; @@ -1950,7 +1940,6 @@ class SelectionListWithIcon { } `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListWithIndirectChildOptions { @ViewChildren(MatListOption) optionInstances!: QueryList; @@ -1963,7 +1952,6 @@ class SelectionListWithIndirectChildOptions { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListOptionWithTwoWayBinding { selected = false; diff --git a/src/material/list/selection-list.ts b/src/material/list/selection-list.ts index b307a394dd62..8f44513aa748 100644 --- a/src/material/list/selection-list.ts +++ b/src/material/list/selection-list.ts @@ -9,10 +9,11 @@ import {FocusKeyManager} from '@angular/cdk/a11y'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {SelectionModel} from '@angular/cdk/collections'; -import {A, ENTER, SPACE, DOWN_ARROW, UP_ARROW, hasModifierKey} from '@angular/cdk/keycodes'; +import {A, ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes'; import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform'; import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, @@ -71,6 +72,7 @@ export class MatSelectionListChange { {provide: MatListBase, useExisting: MatSelectionList}, {provide: SELECTION_LIST, useExisting: MatSelectionList}, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatSelectionList extends MatListBase @@ -159,6 +161,8 @@ export class MatSelectionList private readonly _changeDetectorRef = inject(ChangeDetectorRef); + constructor(...args: unknown[]); + constructor() { super(); this._isNonInteractive = false; @@ -186,7 +190,7 @@ export class MatSelectionList this._watchForSelectionChange(); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { const disabledChanges = changes['disabled']; const disableRippleChanges = changes['disableRipple']; const hideSingleSelectionIndicatorChanges = changes['hideSingleSelectionIndicator']; @@ -365,18 +369,6 @@ export class MatSelectionList _handleKeydown(event: KeyboardEvent) { const activeItem = this._keyManager.activeItem; - if (activeItem && (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW)) { - const focusedElement = _getFocusedElementPierceShadowDom(); - const isOptionFocused = this._items - .toArray() - .some(item => item._elementRef.nativeElement === focusedElement); - if (!isOptionFocused) { - event.preventDefault(); - activeItem.focus(); - return; - } - } - if ( (event.keyCode === ENTER || event.keyCode === SPACE) && !this._keyManager.isTyping() && @@ -411,6 +403,10 @@ export class MatSelectionList /** Handles focusin events within the list. */ private _handleFocusin = (event: FocusEvent) => { + if (this.disabled) { + return; + } + const activeIndex = this._items .toArray() .findIndex(item => item._elementRef.nativeElement.contains(event.target as HTMLElement)); @@ -440,7 +436,7 @@ export class MatSelectionList .withHomeAndEnd() .withTypeAhead() .withWrap() - .skipPredicate(() => false); + .skipPredicate(() => this.disabled); // Set the initial focus. this._resetActiveOption(); @@ -463,15 +459,7 @@ export class MatSelectionList * @param index Index of the active option. If set to -1, no option will be active. */ private _setActiveOption(index: number) { - this._items.forEach((item, itemIndex) => { - let tabindex = -1; - - if (!this.disabled && itemIndex === index) { - tabindex = 0; - } - - item._setTabindex(tabindex); - }); + this._items.forEach((item, itemIndex) => item._setTabindex(itemIndex === index ? 0 : -1)); this._keyManager.updateActiveItem(index); } @@ -481,9 +469,7 @@ export class MatSelectionList */ private _resetActiveOption() { if (this.disabled) { - const activeItem = this._items.find(item => item.selected) || this._items.first; - const index = activeItem ? this._items.toArray().indexOf(activeItem) : -1; - this._setActiveOption(index); + this._setActiveOption(-1); return; } diff --git a/src/material/list/testing/list-harness.spec.ts b/src/material/list/testing/list-harness.spec.ts index bb0c16cde291..281df3ceca5d 100644 --- a/src/material/list/testing/list-harness.spec.ts +++ b/src/material/list/testing/list-harness.spec.ts @@ -6,7 +6,7 @@ import { parallel, } from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, Type, WritableSignal, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, Type, WritableSignal, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {MatDividerHarness} from '../../divider/testing'; import {MatListModule} from '../../list'; @@ -526,7 +526,6 @@ describe('MatSelectionListHarness', () => { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ListHarnessTest { disableThirdItem = signal(false); @@ -567,7 +566,6 @@ class ListHarnessTest { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ActionListHarnessTest { lastClicked!: string; @@ -610,7 +608,6 @@ class ActionListHarnessTest { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NavListHarnessTest { lastClicked!: string; @@ -654,7 +651,6 @@ class NavListHarnessTest { `, imports: [MatListModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SelectionListHarnessTest { disableThirdItem = signal(false); diff --git a/src/material/menu/context-menu-trigger.spec.ts b/src/material/menu/context-menu-trigger.spec.ts index 14a7e253fc5f..8bf46c6651a1 100644 --- a/src/material/menu/context-menu-trigger.spec.ts +++ b/src/material/menu/context-menu-trigger.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; import {MatContextMenuTrigger} from './context-menu-trigger'; import {MatMenu} from './menu'; @@ -16,10 +16,6 @@ describe('context menu trigger', () => { return document.querySelector('.mat-mdc-menu-panel'); } - function getBackdrop(): HTMLElement | null { - return document.querySelector('.cdk-overlay-backdrop'); - } - beforeEach(() => { fixture = TestBed.createComponent(ContextMenuTest); fixture.detectChanges(); @@ -161,21 +157,6 @@ describe('context menu trigger', () => { fixture.detectChanges(); expect(getMenu()).toBe(null); }); - - it('should not create a backdrop for context menu by default', () => { - dispatchMouseEvent(getTrigger(), 'contextmenu', 10, 10); - fixture.detectChanges(); - expect(getBackdrop()).toBeFalsy(); - expect(getMenu()).toBeTruthy(); - }); - - it('should create a backdrop for context menus if the menu explicitly opted into it', () => { - fixture.componentInstance.menuInstance.hasBackdrop = true; - dispatchMouseEvent(getTrigger(), 'contextmenu', 10, 10); - fixture.detectChanges(); - expect(getBackdrop()).toBeTruthy(); - expect(getMenu()).toBeTruthy(); - }); }); @Component({ @@ -202,10 +183,8 @@ describe('context menu trigger', () => { outline: solid 1px; } `, - changeDetection: ChangeDetectionStrategy.Eager, }) class ContextMenuTest { - @ViewChild(MatMenu) menuInstance!: MatMenu; showTrigger = signal(true); disabled = signal(false); opened = jasmine.createSpy('opened'); diff --git a/src/material/menu/menu-content.ts b/src/material/menu/menu-content.ts index ff99bda85d31..ef6a3e3683a6 100644 --- a/src/material/menu/menu-content.ts +++ b/src/material/menu/menu-content.ts @@ -48,6 +48,10 @@ export class MatMenuContent implements OnDestroy { /** Emits when the menu content has been attached. */ readonly _attached = new Subject(); + constructor(...args: unknown[]); + + constructor() {} + /** * Attaches the content with a particular context. * @docs-private diff --git a/src/material/menu/menu-item.ts b/src/material/menu/menu-item.ts index e16ea30a4114..d59e34a54f1a 100644 --- a/src/material/menu/menu-item.ts +++ b/src/material/menu/menu-item.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ElementRef, OnDestroy, @@ -42,6 +43,7 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; '(click)': '_checkDisabled($event)', '(mouseenter)': '_handleMouseEnter()', }, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, templateUrl: 'menu-item.html', imports: [MatRipple], @@ -74,6 +76,8 @@ export class MatMenuItem implements FocusableOption, AfterViewInit, OnDestroy { /** Whether the menu item acts as a trigger for a sub-menu. */ _triggersSubmenu: boolean = false; + constructor(...args: unknown[]); + constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); this._parentMenu?.addItem?.(this); diff --git a/src/material/menu/menu-trigger-base.ts b/src/material/menu/menu-trigger-base.ts index 6e8bf225b59c..1519ceeccd4d 100644 --- a/src/material/menu/menu-trigger-base.ts +++ b/src/material/menu/menu-trigger-base.ts @@ -134,8 +134,6 @@ export abstract class MatMenuTriggerBase implements OnDestroy { this._parentMaterialMenu.closed.emit(reason); } }); - } else { - this._destroyMenu(); } this._menuItemInstance?._setTriggersSubmenu(this._triggersSubmenu()); @@ -224,7 +222,7 @@ export abstract class MatMenuTriggerBase implements OnDestroy { overlayConfig.hasBackdrop = menu.hasBackdrop == null ? !this._triggersSubmenu() : menu.hasBackdrop; } else { - overlayConfig.hasBackdrop = menu.hasBackdrop ?? false; + overlayConfig.hasBackdrop = false; } // We need the `hasAttached` check for the case where the user kicked off a removal animation, diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index 16b56ae4b12e..7d4cb842557a 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -97,6 +97,8 @@ export class MatMenuTrigger extends MatMenuTriggerBase implements AfterContentIn // tslint:disable-next-line:no-output-on-prefix @Output() readonly onMenuClose: EventEmitter = this.menuClosed; + constructor(...args: unknown[]); + constructor() { super(true); diff --git a/src/material/menu/menu.md b/src/material/menu/menu.md index b3ded2c9ecc7..c0f5f11170af 100644 --- a/src/material/menu/menu.md +++ b/src/material/menu/menu.md @@ -57,10 +57,6 @@ area and will be closed if the user clicks away. You can set up a `mat-menu` as a context menu by adding the `matContextMenuTriggerFor` directive to your container and binding it to a menu instance. -Note that by default context menus don't create a backdrop element in order to allow the user to -right click somewhere else. You can opt into the backdrop by explicitly setting -`[hasBackdrop]="true"` on the menu. - ### Lazy rendering diff --git a/src/material/menu/menu.spec.ts b/src/material/menu/menu.spec.ts index 969a34725eb3..14c1ecb99d97 100644 --- a/src/material/menu/menu.spec.ts +++ b/src/material/menu/menu.spec.ts @@ -28,7 +28,7 @@ import { ViewChild, ViewChildren, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import { @@ -59,10 +59,6 @@ const MENU_PANEL_TOP_PADDING = 8; describe('MatMenu', () => { let overlayContainerElement: HTMLElement; - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - beforeEach(() => { TestBed.configureTestingModule({ providers: [{provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}], @@ -76,17 +72,18 @@ describe('MatMenu', () => { window.scroll(0, 0); }); - it('should set aria-controls on the menu panel', () => { + it('should aria-controls the menu panel', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(fixture.componentInstance.triggerEl.nativeElement.getAttribute('aria-controls')).toBe( fixture.componentInstance.menu.panelId, ); - }); + })); - it('should set aria-haspopup based on whether a menu is assigned', () => { + it('should set aria-haspopup based on whether a menu is assigned', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerElement = fixture.componentInstance.triggerEl.nativeElement; @@ -98,9 +95,9 @@ describe('MatMenu', () => { fixture.detectChanges(); expect(triggerElement.hasAttribute('aria-haspopup')).toBe(false); - }); + })); - it('should open the menu as an idempotent operation', () => { + it('should open the menu as an idempotent operation', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); expect(overlayContainerElement.textContent).toBe(''); @@ -108,13 +105,14 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.textContent).toContain('Item'); expect(overlayContainerElement.textContent).toContain('Disabled'); }).not.toThrowError(); - }); + })); - it('should close the menu when a click occurs outside the menu', async () => { + it('should close the menu when a click occurs outside the menu', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); @@ -122,44 +120,45 @@ describe('MatMenu', () => { const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop'); backdrop.click(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlayContainerElement.textContent).toBe(''); - }); + })); - it('should be able to remove the backdrop', () => { + it('should be able to remove the backdrop', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.menu.hasBackdrop = false; fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); - }); + })); - it('should set the correct aria-haspopup value on the trigger element', () => { + it('should set the correct aria-haspopup value on the trigger element', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerElement = fixture.componentInstance.triggerEl.nativeElement; expect(triggerElement.getAttribute('aria-haspopup')).toBe('menu'); - }); + })); - it('should be able to remove the backdrop on repeat openings', async () => { + it('should be able to remove the backdrop on repeat openings', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); // Start off with a backdrop. expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); // Change `hasBackdrop` after the first open. fixture.componentInstance.menu.hasBackdrop = false; @@ -168,12 +167,12 @@ describe('MatMenu', () => { // Reopen the menu. fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); - }); + })); - it('should restore focus to the trigger when the menu was opened by keyboard', () => { + it('should restore focus to the trigger when the menu was opened by keyboard', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; @@ -186,11 +185,12 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe(triggerEl); - }); + })); - it('should not restore focus to the trigger if focus restoration is disabled', () => { + it('should not restore focus to the trigger if focus restoration is disabled', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; @@ -207,11 +207,12 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).not.toBe(triggerEl); - }); + })); - it('should be able to move focus in the closed event', () => { + it('should be able to move focus in the closed event', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); const instance = fixture.componentInstance; fixture.detectChanges(); @@ -226,13 +227,14 @@ describe('MatMenu', () => { const subscription = instance.trigger.menuClosed.subscribe(() => button.focus()); instance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe(button); button.remove(); subscription.unsubscribe(); - }); + })); - it('should restore focus to the trigger immediately once the menu is closed', () => { + it('should restore focus to the trigger immediately once the menu is closed', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; @@ -240,24 +242,27 @@ describe('MatMenu', () => { // A click without a mousedown before it is considered a keyboard open. triggerEl.click(); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel')).toBeTruthy(); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - // Note: don't add a `whenStable` here since we're testing + // Note: don't add a `tick` here since we're testing // that focus is restored before the animation is done. expect(document.activeElement).toBe(triggerEl); - }); + tick(500); + })); - it('should move focus to another item if the active item is destroyed', () => { + it('should move focus to another item if the active item is destroyed', fakeAsync(() => { const fixture = TestBed.createComponent(MenuWithRepeatedItems); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; triggerEl.click(); fixture.detectChanges(); + tick(500); const items = overlayContainerElement.querySelectorAll( '.mat-mdc-menu-panel .mat-mdc-menu-item', @@ -269,24 +274,26 @@ describe('MatMenu', () => { fixture.componentInstance.items.shift(); fixture.detectChanges(); + tick(500); expect(spy).toHaveBeenCalled(); - }); + })); - it('should be able to set a custom class on the backdrop', () => { + it('should be able to set a custom class on the backdrop', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.componentInstance.backdropClass = 'custom-backdrop'; fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop'); expect(backdrop.classList).toContain('custom-backdrop'); - }); + })); - it('should be able to set a custom class on the overlay panel', () => { + it('should be able to set a custom class on the overlay panel', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -301,13 +308,14 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); expect(overlayPane.classList).toContain('custom-panel-class'); - }); + })); - it('should be able to set a custom classes on the overlay panel', () => { + it('should be able to set a custom classes on the overlay panel', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -321,14 +329,15 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); expect(overlayPane.classList).toContain('custom-panel-class-1'); expect(overlayPane.classList).toContain('custom-panel-class-2'); - }); + })); - it('should restore focus to the root trigger when the menu was opened by mouse', () => { + it('should restore focus to the root trigger when the menu was opened by mouse', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -341,11 +350,12 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe(triggerEl); - }); + })); - it('should restore focus to the root trigger when the menu was opened by touch', () => { + it('should restore focus to the root trigger when the menu was opened by touch', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -358,11 +368,12 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + flush(); expect(document.activeElement).toBe(triggerEl); - }); + })); - it('should scroll the panel to the top on open, when it is scrollable', () => { + it('should scroll the panel to the top on open, when it is scrollable', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -376,10 +387,13 @@ describe('MatMenu', () => { triggerEl.click(); fixture.detectChanges(); + // Flush due to the additional tick that is necessary for the FocusMonitor. + flush(); + expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel')!.scrollTop).toBe(0); - }); + })); - it('should set the proper focus origin when restoring focus after opening by keyboard', () => { + it('should set the proper focus origin when restoring focus after opening by keyboard', fakeAsync(() => { const focusMonitor = TestBed.inject(FocusMonitor); const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -391,13 +405,14 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); fixture.detectChanges(); expect(triggerEl.classList).toContain('cdk-program-focused'); focusMonitor.stopMonitoring(triggerEl); - }); + })); - it('should set the proper focus origin when restoring focus after opening by mouse', () => { + it('should set the proper focus origin when restoring focus after opening by mouse', fakeAsync(() => { const focusMonitor = TestBed.inject(FocusMonitor); const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -410,13 +425,14 @@ describe('MatMenu', () => { focusMonitor.monitor(triggerEl, false); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); fixture.detectChanges(); expect(triggerEl.classList).toContain('cdk-mouse-focused'); focusMonitor.stopMonitoring(triggerEl); - }); + })); - it('should set proper focus origin when right clicking on trigger, before opening by keyboard', () => { + it('should set proper focus origin when right clicking on trigger, before opening by keyboard', fakeAsync(() => { const focusMonitor = TestBed.inject(FocusMonitor); const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -434,13 +450,14 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); fixture.detectChanges(); expect(triggerEl.classList).toContain('cdk-program-focused'); focusMonitor.stopMonitoring(triggerEl); - }); + })); - it('should set the proper focus origin when restoring focus after opening by touch', () => { + it('should set the proper focus origin when restoring focus after opening by touch', fakeAsync(() => { const focusMonitor = TestBed.inject(FocusMonitor); const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -453,13 +470,15 @@ describe('MatMenu', () => { focusMonitor.monitor(triggerEl, false); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); fixture.detectChanges(); + flush(); expect(triggerEl.classList).toContain('cdk-touch-focused'); focusMonitor.stopMonitoring(triggerEl); - }); + })); - it('should close the menu when pressing ESCAPE', async () => { + it('should close the menu when pressing ESCAPE', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); @@ -468,13 +487,13 @@ describe('MatMenu', () => { const event = dispatchKeyboardEvent(panel, 'keydown', ESCAPE); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlayContainerElement.textContent).toBe(''); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not close the menu when pressing ESCAPE with a modifier', () => { + it('should not close the menu when pressing ESCAPE with a modifier', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); @@ -484,10 +503,11 @@ describe('MatMenu', () => { dispatchEvent(panel, event); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.textContent).toBeTruthy(); expect(event.defaultPrevented).toBe(false); - }); + })); it('should open a custom menu', () => { const fixture = TestBed.createComponent(CustomMenu); @@ -502,7 +522,7 @@ describe('MatMenu', () => { }).not.toThrowError(); }); - it('should set the panel direction based on the trigger direction', () => { + it('should set the panel direction based on the trigger direction', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [provideFakeDirectionality('rtl')], }); @@ -511,14 +531,15 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const boundingBox = overlayContainerElement.querySelector( '.cdk-overlay-connected-position-bounding-box', )!; expect(boundingBox.getAttribute('dir')).toEqual('rtl'); - }); + })); - it('should update the panel direction if the trigger direction changes', async () => { + it('should update the panel direction if the trigger direction changes', fakeAsync(() => { const dir = signal('rtl'); TestBed.resetTestingModule().configureTestingModule({ providers: [provideFakeDirectionality(dir)], @@ -528,7 +549,7 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); let boundingBox = overlayContainerElement.querySelector( '.cdk-overlay-connected-position-bounding-box', @@ -537,26 +558,27 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); dir.set('ltr'); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); boundingBox = overlayContainerElement.querySelector( '.cdk-overlay-connected-position-bounding-box', )!; expect(boundingBox.getAttribute('dir')).toEqual('ltr'); - }); + })); - it('should transfer any custom classes from the host to the overlay', () => { + it('should transfer any custom classes from the host to the overlay', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.componentInstance.panelClass = 'custom-one custom-two'; fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const menuEl = fixture.debugElement.query(By.css('mat-menu'))!.nativeElement; const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel')!; @@ -566,22 +588,14 @@ describe('MatMenu', () => { expect(panel.classList).toContain('custom-one'); expect(panel.classList).toContain('custom-two'); - }); - - it('should expose the configured classes via the getter', () => { - const fixture = TestBed.createComponent(SimpleMenu); - fixture.componentInstance.panelClass = 'custom-one custom-two'; - fixture.detectChanges(); - - expect(fixture.componentInstance.menu.panelClass).toBe('custom-one custom-two'); - expect(fixture.componentInstance.menu.classList).toBe('custom-one custom-two'); - }); + })); - it('should set the "menu" role on the overlay panel', () => { + it('should set the "menu" role on the overlay panel', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const menuPanel = overlayContainerElement.querySelector('.mat-mdc-menu-panel'); @@ -589,14 +603,15 @@ describe('MatMenu', () => { const role = menuPanel ? menuPanel.getAttribute('role') : ''; expect(role).withContext('Expected panel to have the "menu" role.').toBe('menu'); - }); + })); - it('should forward ARIA attributes to the menu panel', () => { + it('should forward ARIA attributes to the menu panel', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); const instance = fixture.componentInstance; fixture.detectChanges(); instance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const menuPanel = overlayContainerElement.querySelector('.mat-mdc-menu-panel')!; expect(menuPanel.hasAttribute('aria-label')).toBe(false); @@ -623,37 +638,40 @@ describe('MatMenu', () => { expect(menuPanel.hasAttribute('aria-label')).toBe(false); expect(menuPanel.hasAttribute('aria-labelledby')).toBe(false); expect(menuPanel.hasAttribute('aria-describedby')).toBe(false); - }); + })); - it('should set the "menuitem" role on the items by default', () => { + it('should set the "menuitem" role on the items by default', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const items = Array.from(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item')); expect(items.length).toBeGreaterThan(0); expect(items.every(item => item.getAttribute('role') === 'menuitem')).toBe(true); - }); + })); - it('should be able to set an alternate role on the menu items', () => { + it('should be able to set an alternate role on the menu items', fakeAsync(() => { const fixture = TestBed.createComponent(MenuWithCheckboxItems); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const items = Array.from(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item')); expect(items.length).toBeGreaterThan(0); expect(items.every(item => item.getAttribute('role') === 'menuitemcheckbox')).toBe(true); - }); + })); - it('should not change focus origin if origin not specified for menu items', () => { + it('should not change focus origin if origin not specified for menu items', fakeAsync(() => { const fixture = TestBed.createComponent(MenuWithCheckboxItems); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); let [firstMenuItemDebugEl, secondMenuItemDebugEl] = fixture.debugElement.queryAll( By.css('.mat-mdc-menu-item'), @@ -666,10 +684,11 @@ describe('MatMenu', () => { firstMenuItemInstance.focus('mouse'); secondMenuItemDebugEl.nativeElement.blur(); secondMenuItemInstance.focus(); + tick(500); expect(secondMenuItemDebugEl.nativeElement.classList).toContain('cdk-focused'); expect(secondMenuItemDebugEl.nativeElement.classList).toContain('cdk-mouse-focused'); - }); + })); it('should not throw an error on destroy', () => { const fixture = TestBed.createComponent(SimpleMenu); @@ -682,21 +701,21 @@ describe('MatMenu', () => { expect(fixture.componentInstance.items.first.getLabel()).toBe('Item'); }); - it('should filter out icon nodes when figuring out the label', () => { + it('should filter out icon nodes when figuring out the label', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const items = fixture.componentInstance.items.toArray(); expect(items[2].getLabel()).toBe('Item with an icon'); - }); + })); - it('should get the label of an item if the text is not in a direct descendant node', () => { + it('should get the label of an item if the text is not in a direct descendant node', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const items = fixture.componentInstance.items.toArray(); expect(items[3].getLabel()).toBe('Item with text inside span'); - }); + })); - it('should set the proper focus origin when opening by mouse', () => { + it('should set the proper focus origin when opening by mouse', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); spyOn(fixture.componentInstance.items.first, 'focus').and.callThrough(); @@ -706,11 +725,12 @@ describe('MatMenu', () => { dispatchMouseEvent(triggerEl, 'mousedown'); triggerEl.click(); fixture.detectChanges(); + tick(500); expect(fixture.componentInstance.items.first.focus).toHaveBeenCalledWith('mouse'); - }); + })); - it('should set the proper focus origin when opening by touch', () => { + it('should set the proper focus origin when opening by touch', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); spyOn(fixture.componentInstance.items.first, 'focus').and.callThrough(); @@ -720,9 +740,10 @@ describe('MatMenu', () => { dispatchMouseEvent(triggerEl, 'touchstart'); triggerEl.click(); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.items.first.focus).toHaveBeenCalledWith('touch'); - }); + })); it('should set the proper origin when calling focusFirstItem after the opening sequence has started', () => { const fixture = TestBed.createComponent(SimpleMenu); @@ -737,7 +758,7 @@ describe('MatMenu', () => { expect(fixture.componentInstance.items.first.focus).toHaveBeenCalledOnceWith('touch'); }); - it('should close the menu when using the CloseScrollStrategy', () => { + it('should close the menu when using the CloseScrollStrategy', fakeAsync(() => { const scrolledSubject = new Subject(); TestBed.resetTestingModule().configureTestingModule({ providers: [ @@ -760,11 +781,12 @@ describe('MatMenu', () => { expect(trigger.menuOpen).toBe(true); scrolledSubject.next(); + tick(500); expect(trigger.menuOpen).toBe(false); - }); + })); - it('should switch to keyboard focus when using the keyboard after opening using the mouse', () => { + it('should switch to keyboard focus when using the keyboard after opening using the mouse', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -778,18 +800,23 @@ describe('MatMenu', () => { items.forEach(item => patchElementFocus(item)); + tick(500); + tick(); fixture.detectChanges(); expect(items.some(item => item.classList.contains('cdk-keyboard-focused'))).toBe(false); dispatchKeyboardEvent(panel, 'keydown', DOWN_ARROW); fixture.detectChanges(); + // Flush due to the additional tick that is necessary for the FocusMonitor. + flush(); + // We skip to the third item, because the second one is disabled. expect(items[2].classList).toContain('cdk-focused'); expect(items[2].classList).toContain('cdk-keyboard-focused'); - }); + })); - it('should set the keyboard focus origin when opened using the keyboard', () => { + it('should set the keyboard focus origin when opened using the keyboard', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; @@ -804,12 +831,14 @@ describe('MatMenu', () => { ); items.forEach(item => patchElementFocus(item)); + tick(500); + tick(); fixture.detectChanges(); expect(items[0].classList).toContain('cdk-keyboard-focused'); - }); + })); - it('should toggle the aria-expanded attribute on the trigger', () => { + it('should toggle the aria-expanded attribute on the trigger', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; @@ -818,16 +847,18 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(triggerEl.getAttribute('aria-expanded')).toBe('true'); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(triggerEl.getAttribute('aria-expanded')).toBe('false'); - }); + })); - it('should toggle aria-expanded on the trigger in an OnPush component', () => { + it('should toggle aria-expanded on the trigger in an OnPush component', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenuOnPush); fixture.detectChanges(); const triggerEl = fixture.componentInstance.triggerEl.nativeElement; @@ -836,23 +867,26 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(triggerEl.getAttribute('aria-expanded')).toBe('true'); fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(triggerEl.getAttribute('aria-expanded')).toBe('false'); - }); + })); - it('should throw if assigning a menu that contains the trigger', async () => { + it('should throw if assigning a menu that contains the trigger', fakeAsync(() => { expect(() => { const fixture = TestBed.createComponent(InvalidRecursiveMenu); fixture.detectChanges(); + tick(500); }).toThrowError(/menu cannot contain its own trigger/); - }); + })); - it('should be able to swap out a menu after the first time it is opened', async () => { + it('should be able to swap out a menu after the first time it is opened', fakeAsync(() => { const fixture = TestBed.createComponent(DynamicPanelMenu); fixture.detectChanges(); expect(overlayContainerElement.textContent).toBe(''); @@ -865,7 +899,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); fixture.detectChanges(); expect(overlayContainerElement.textContent).toBe(''); @@ -879,13 +913,13 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); fixture.detectChanges(); expect(overlayContainerElement.textContent).toBe(''); - }); + })); - it('should focus the first item when pressing home', () => { + it('should focus the first item when pressing home', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -910,9 +944,10 @@ describe('MatMenu', () => { expect(items[0].focus).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); - }); + flush(); + })); - it('should not focus the first item when pressing home with a modifier key', () => { + it('should not focus the first item when pressing home with a modifier key', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -936,9 +971,10 @@ describe('MatMenu', () => { expect(items[0].focus).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(false); - }); + flush(); + })); - it('should focus the last item when pressing end', () => { + it('should focus the last item when pressing end', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -956,9 +992,10 @@ describe('MatMenu', () => { expect(items[items.length - 1].focus).toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); - }); + flush(); + })); - it('should not focus the last item when pressing end with a modifier key', () => { + it('should not focus the last item when pressing end with a modifier key', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -978,14 +1015,16 @@ describe('MatMenu', () => { expect(items[items.length - 1].focus).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(false); - }); + flush(); + })); - it('should respect the DOM order, rather than insertion order, when moving focus using the arrow keys', () => { + it('should respect the DOM order, rather than insertion order, when moving focus using the arrow keys', fakeAsync(() => { let fixture = TestBed.createComponent(SimpleMenuWithRepeater); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); let menuPanel = document.querySelector('.mat-mdc-menu-panel')!; let items = menuPanel.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'); @@ -1002,11 +1041,13 @@ describe('MatMenu', () => { items = menuPanel.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'); dispatchKeyboardEvent(menuPanel, 'keydown', DOWN_ARROW); fixture.detectChanges(); + tick(); expect(document.activeElement).withContext('Expected second item to be focused').toBe(items[1]); - }); + flush(); + })); - it('should sync the focus order when an item is focused programmatically', () => { + it('should sync the focus order when an item is focused programmatically', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenuWithRepeater); // Add some more items to work with. @@ -1017,6 +1058,7 @@ describe('MatMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const menuPanel = document.querySelector('.mat-mdc-menu-panel')!; const items = menuPanel.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'); @@ -1032,75 +1074,81 @@ describe('MatMenu', () => { dispatchKeyboardEvent(menuPanel, 'keydown', DOWN_ARROW); fixture.detectChanges(); + tick(); expect(document.activeElement).withContext('Expected fifth item to be focused').toBe(items[4]); - }); + flush(); + })); - it('should open submenus when the menu is inside an OnPush component', () => { + it('should open submenus when the menu is inside an OnPush component', fakeAsync(() => { const fixture = TestBed.createComponent(LazyMenuWithOnPush); fixture.detectChanges(); // Open the top-level menu fixture.componentInstance.rootTrigger.nativeElement.click(); fixture.detectChanges(); + flush(); // Dispatch a `mouseenter` on the menu item to open the submenu. // This will only work if the top-level menu is aware the this menu item exists. dispatchMouseEvent(fixture.componentInstance.menuItemWithSubmenu.nativeElement, 'mouseenter'); fixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item').length) .withContext('Expected two open menus') .toBe(2); - }); + })); - it('should focus the menu panel if all items are disabled', () => { + it('should focus the menu panel if all items are disabled', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenuWithRepeater); fixture.componentInstance.items.forEach(item => (item.disabled = true)); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe( overlayContainerElement.querySelector('.mat-mdc-menu-panel'), ); - }); + })); - it('should focus the menu panel if all items are disabled inside lazy content', () => { + it('should focus the menu panel if all items are disabled inside lazy content', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenuWithRepeaterInLazyContent); fixture.componentInstance.items.forEach(item => (item.disabled = true)); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe( overlayContainerElement.querySelector('.mat-mdc-menu-panel'), ); - }); + })); - it('should clear the static aria-label from the menu host', () => { + it('should clear the static aria-label from the menu host', fakeAsync(() => { const fixture = TestBed.createComponent(StaticAriaLabelMenu); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('mat-menu').hasAttribute('aria-label')).toBe(false); - }); + })); - it('should clear the static aria-labelledby from the menu host', () => { + it('should clear the static aria-labelledby from the menu host', fakeAsync(() => { const fixture = TestBed.createComponent(StaticAriaLabelledByMenu); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('mat-menu').hasAttribute('aria-labelledby')).toBe( false, ); - }); + })); - it('should clear the static aria-describedby from the menu host', () => { + it('should clear the static aria-describedby from the menu host', fakeAsync(() => { const fixture = TestBed.createComponent(StaticAriaDescribedbyMenu); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('mat-menu').hasAttribute('aria-describedby')).toBe( false, ); - }); + })); - it('should be able to move focus inside the `open` event', () => { + it('should be able to move focus inside the `open` event', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); @@ -1109,21 +1157,23 @@ describe('MatMenu', () => { }); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const items = document.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'); expect(document.activeElement).withContext('Expected fourth item to be focused').toBe(items[3]); - }); + })); - it('should default to the "below" and "after" positions', () => { + it('should default to the "below" and "after" positions', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; expect(panel.classList).toContain('mat-menu-below'); expect(panel.classList).toContain('mat-menu-after'); - }); + })); it('should keep the panel in the viewport when more items are added while open', () => { const viewportRuler = TestBed.inject(ViewportRuler); @@ -1149,26 +1199,13 @@ describe('MatMenu', () => { expect(Math.floor(panelRect.bottom)).toBe(viewportHeight); }); - it('should close the menu when it is cleared from the trigger', async () => { - const fixture = TestBed.createComponent(SimpleMenu); - fixture.detectChanges(); - fixture.componentInstance.trigger.openMenu(); - fixture.detectChanges(); - await wait(200); - expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel')).toBeTruthy(); - - fixture.componentInstance.trigger.menu = null; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel')).toBeFalsy(); - }); - describe('lazy rendering', () => { - it('should be able to render the menu content lazily', () => { + it('should be able to render the menu content lazily', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleLazyMenu); fixture.detectChanges(); fixture.componentInstance.triggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel')!; @@ -1179,14 +1216,14 @@ describe('MatMenu', () => { expect(fixture.componentInstance.trigger.menuOpen) .withContext('Expected menu to be open') .toBe(true); - }); + })); - it('should detach the lazy content when the menu is closed', async () => { + it('should detach the lazy content when the menu is closed', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleLazyMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); fixture.detectChanges(); const checkers = fixture.componentInstance.destroyCheckers.toArray(); @@ -1195,7 +1232,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); fixture.detectChanges(); expect(fixture.componentInstance.items.length) @@ -1204,7 +1241,7 @@ describe('MatMenu', () => { expect(checkers.map(d => d.destroyed)) .withContext('Expected ngOnDestroy to have been called') .toEqual([true, true]); - }); + })); it('should focus the first menu item when opening a lazy menu via keyboard', () => { const fixture = TestBed.createComponent(SimpleLazyMenu); @@ -1219,11 +1256,12 @@ describe('MatMenu', () => { expect(document.activeElement).withContext('Expected first item to be focused').toBe(item); }); - it('should be able to open the same menu with a different context', () => { + it('should be able to open the same menu with a different context', fakeAsync(() => { const fixture = TestBed.createComponent(LazyMenuWithContext); fixture.detectChanges(); fixture.componentInstance.triggerOne.openMenu(); fixture.detectChanges(); + tick(500); let item = overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')!; @@ -1231,35 +1269,39 @@ describe('MatMenu', () => { fixture.componentInstance.triggerOne.closeMenu(); fixture.detectChanges(); + tick(500); fixture.componentInstance.triggerTwo.openMenu(); fixture.detectChanges(); + tick(500); item = overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')!; expect(item.textContent!.trim()).toBe('two'); - }); + })); }); - it('does not open if the trigger element is disabled (including disabledInteractive)', () => { + it('does not open if the trigger element is disabled (including disabledInteractive)', fakeAsync(() => { const fixture = TestBed.createComponent(DisabledMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; trigger.click(); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull(); dispatchKeyboardEvent(trigger, 'keydown', ENTER); trigger.click(); fixture.detectChanges(); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-menu-panel [mat-menu-item]')).toBeNull(); - }); + })); describe('positions', () => { let fixture: ComponentFixture; let trigger: HTMLElement; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(PositionedMenu); fixture.detectChanges(); @@ -1271,11 +1313,12 @@ describe('MatMenu', () => { // Push trigger to the right, so it has space to open "before" trigger.style.left = '100px'; - }); + })); - it('should append mat-menu-before if the x position is changed', () => { + it('should append mat-menu-before if the x position is changed', fakeAsync(() => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; @@ -1288,11 +1331,12 @@ describe('MatMenu', () => { expect(panel.classList).toContain('mat-menu-after'); expect(panel.classList).not.toContain('mat-menu-before'); - }); + })); - it('should append mat-menu-above if the y position is changed', () => { + it('should append mat-menu-above if the y position is changed', fakeAsync(() => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; @@ -1305,15 +1349,15 @@ describe('MatMenu', () => { expect(panel.classList).toContain('mat-menu-below'); expect(panel.classList).not.toContain('mat-menu-above'); - }); + })); - it('should update the position classes if the window is resized', async () => { + it('should update the position classes if the window is resized', fakeAsync(() => { trigger.style.position = 'fixed'; trigger.style.top = '300px'; fixture.componentInstance.yPosition = 'above'; fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); const panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; @@ -1323,14 +1367,14 @@ describe('MatMenu', () => { trigger.style.top = '0'; dispatchFakeEvent(window, 'resize'); fixture.detectChanges(); - await wait(200); + tick(500); fixture.detectChanges(); expect(panel.classList).not.toContain('mat-menu-above'); expect(panel.classList).toContain('mat-menu-below'); - }); + })); - it('should be able to update the position after the first open', async () => { + it('should be able to update the position after the first open', fakeAsync(() => { trigger.style.position = 'fixed'; trigger.style.top = '200px'; @@ -1340,7 +1384,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); let panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; @@ -1350,7 +1394,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); fixture.componentInstance.yPosition = 'below'; fixture.changeDetectorRef.markForCheck(); @@ -1358,13 +1402,13 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); panel = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; expect(Math.floor(panel.getBoundingClientRect().top)) .withContext('Expected menu to open below') .toBe(Math.floor(trigger.getBoundingClientRect().bottom)); - }); + })); it('should not throw if a menu reposition is requested while the menu is closed', () => { expect(() => fixture.componentInstance.trigger.updatePosition()).not.toThrow(); @@ -1372,7 +1416,7 @@ describe('MatMenu', () => { }); describe('fallback positions', () => { - it('should fall back to "before" mode if "after" mode would not fit on screen', () => { + it('should fall back to "before" mode if "after" mode would not fit on screen', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; @@ -1385,6 +1429,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = getOverlayPane(); const triggerRect = trigger.getBoundingClientRect(); const overlayRect = overlayPane.getBoundingClientRect(); @@ -1403,9 +1448,9 @@ describe('MatMenu', () => { expect(Math.floor(overlayRect.top)) .withContext(`Expected menu top position to be unchanged if it can fit in the viewport.`) .toBe(Math.floor(triggerRect.bottom)); - }); + })); - it('should fall back to "above" mode if "below" mode would not fit on screen', () => { + it('should fall back to "above" mode if "below" mode would not fit on screen', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; @@ -1417,6 +1462,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = getOverlayPane(); const triggerRect = trigger.getBoundingClientRect(); const overlayRect = overlayPane.getBoundingClientRect(); @@ -1430,9 +1476,9 @@ describe('MatMenu', () => { expect(Math.floor(overlayRect.left)) .withContext(`Expected menu x position to be unchanged if it can fit in the viewport.`) .toBe(Math.floor(triggerRect.left)); - }); + })); - it('should re-position menu on both axes if both defaults would not fit', () => { + it('should re-position menu on both axes if both defaults would not fit', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; @@ -1445,6 +1491,7 @@ describe('MatMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = getOverlayPane(); const triggerRect = trigger.getBoundingClientRect(); const overlayRect = overlayPane.getBoundingClientRect(); @@ -1458,15 +1505,16 @@ describe('MatMenu', () => { expect(Math.floor(overlayRect.bottom)) .withContext(`Expected menu to open in "above" position if "below" position wouldn't fit.`) .toBe(Math.floor(triggerRect.top)); - }); + })); - it('should re-position a menu with custom position set', () => { + it('should re-position a menu with custom position set', fakeAsync(() => { const fixture = TestBed.createComponent(PositionedMenu); fixture.detectChanges(); const trigger = fixture.componentInstance.triggerEl.nativeElement; fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const overlayPane = getOverlayPane(); const triggerRect = trigger.getBoundingClientRect(); const overlayRect = overlayPane.getBoundingClientRect(); @@ -1486,7 +1534,7 @@ describe('MatMenu', () => { expect(Math.floor(overlayRect.top)) .withContext(`Expected menu to open in "below" position if "above" position wouldn't fit.`) .toBe(Math.floor(triggerRect.bottom)); - }); + })); function getOverlayPane(): HTMLElement { return overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; @@ -1520,6 +1568,7 @@ describe('MatMenu', () => { openMenu() { this.fixture.componentInstance.trigger.openMenu(); this.fixture.detectChanges(); + tick(500); } get overlayRect() { @@ -1541,151 +1590,162 @@ describe('MatMenu', () => { let subject: OverlapSubject; describe('explicitly overlapping', () => { - beforeEach(() => { + beforeEach(fakeAsync(() => { subject = new OverlapSubject(OverlapMenu, {overlapTrigger: true}); - }); + })); - it('positions the overlay below the trigger', () => { + it('positions the overlay below the trigger', fakeAsync(() => { subject.openMenu(); + // Since the menu is overlaying the trigger, the overlay top should be the trigger top. // Since the menu is overlaying the trigger, the overlay top should be the trigger top. expect(Math.floor(subject.overlayRect.top)) .withContext(`Expected menu to open in default "below" position.`) .toBe(Math.floor(subject.triggerRect.top)); - }); + })); }); describe('not overlapping', () => { - beforeEach(() => { + beforeEach(fakeAsync(() => { subject = new OverlapSubject(OverlapMenu, {overlapTrigger: false}); - }); + })); - it('positions the overlay below the trigger', () => { + it('positions the overlay below the trigger', fakeAsync(() => { subject.openMenu(); + // Since the menu is below the trigger, the overlay top should be the trigger bottom. // Since the menu is below the trigger, the overlay top should be the trigger bottom. expect(Math.floor(subject.overlayRect.top)) .withContext(`Expected menu to open directly below the trigger.`) .toBe(Math.floor(subject.triggerRect.bottom)); - }); + })); - it('supports above position fall back', () => { + it('supports above position fall back', fakeAsync(() => { // Push trigger to the bottom part of viewport, so it doesn't have space to open // in its default "below" position below the trigger. subject.trigger.style.position = 'fixed'; subject.trigger.style.bottom = '0'; subject.openMenu(); + // Since the menu is above the trigger, the overlay bottom should be the trigger top. // Since the menu is above the trigger, the overlay bottom should be the trigger top. expect(Math.floor(subject.overlayRect.bottom)) .withContext( `Expected menu to open in "above" position if "below" position ` + `wouldn't fit.`, ) .toBe(Math.floor(subject.triggerRect.top)); - }); + })); - it('repositions the origin to be below, so the menu opens from the trigger', () => { + it('repositions the origin to be below, so the menu opens from the trigger', fakeAsync(() => { subject.openMenu(); subject.fixture.detectChanges(); expect(subject.menuPanel!.classList).toContain('mat-menu-below'); expect(subject.menuPanel!.classList).not.toContain('mat-menu-above'); - }); + })); }); }); describe('animations', () => { - it('should enable ripples on items by default', () => { + it('should enable ripples on items by default', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const item = fixture.debugElement.query(By.css('.mat-mdc-menu-item'))!; const ripple = item.query(By.css('.mat-ripple'))!.injector.get(MatRipple); expect(ripple.disabled).toBe(false); - }); + })); - it('should disable ripples on disabled items', () => { + it('should disable ripples on disabled items', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const items = fixture.debugElement.queryAll(By.css('.mat-mdc-menu-item')); const ripple = items[1].query(By.css('.mat-ripple'))!.injector.get(MatRipple); expect(ripple.disabled).toBe(true); - }); + })); - it('should disable ripples if disableRipple is set', () => { + it('should disable ripples if disableRipple is set', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); // The third menu item in the `SimpleMenu` component has ripples disabled. const items = fixture.debugElement.queryAll(By.css('.mat-mdc-menu-item')); const ripple = items[2].query(By.css('.mat-ripple'))!.injector.get(MatRipple); expect(ripple.disabled).toBe(true); - }); + })); }); describe('close event', () => { let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - }); + tick(500); + })); - it('should emit an event when a menu item is clicked', () => { + it('should emit an event when a menu item is clicked', fakeAsync(() => { const menuItem = overlayContainerElement.querySelector('[mat-menu-item]') as HTMLElement; menuItem.click(); fixture.detectChanges(); + tick(500); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledWith('click'); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledTimes(1); - }); + })); - it('should emit a close event when the backdrop is clicked', () => { + it('should emit a close event when the backdrop is clicked', fakeAsync(() => { const backdrop = overlayContainerElement.querySelector( '.cdk-overlay-backdrop', ) as HTMLElement; backdrop.click(); fixture.detectChanges(); + tick(500); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledWith(undefined); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledTimes(1); - }); + })); - it('should emit an event when pressing ESCAPE', () => { + it('should emit an event when pressing ESCAPE', fakeAsync(() => { const menu = overlayContainerElement.querySelector('.mat-mdc-menu-panel') as HTMLElement; dispatchKeyboardEvent(menu, 'keydown', ESCAPE); fixture.detectChanges(); + tick(500); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledWith('keydown'); expect(fixture.componentInstance.closeCallback).toHaveBeenCalledTimes(1); - }); + })); - it('should complete the callback when the menu is destroyed', () => { + it('should complete the callback when the menu is destroyed', fakeAsync(() => { const completeCallback = jasmine.createSpy('complete callback'); fixture.componentInstance.menu.closed.subscribe(null, null, completeCallback); fixture.destroy(); + tick(500); expect(completeCallback).toHaveBeenCalled(); - }); + })); }); describe('nested menu', () => { @@ -1715,46 +1775,52 @@ describe('MatMenu', () => { overlay = overlayContainerElement; }); - it('should set the `triggersSubmenu` flags on the triggers', () => { + it('should set the `triggersSubmenu` flags on the triggers', fakeAsync(() => { expect(instance.rootTrigger.triggersSubmenu()).toBe(false); expect(instance.levelOneTrigger.triggersSubmenu()).toBe(true); expect(instance.levelTwoTrigger.triggersSubmenu()).toBe(true); - }); + })); - it('should set the `parentMenu` on the sub-menu instances', () => { + it('should set the `parentMenu` on the sub-menu instances', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelTwoTrigger.openMenu(); fixture.detectChanges(); + tick(500); expect(instance.rootMenu.parentMenu).toBeFalsy(); expect(instance.levelOneMenu.parentMenu).toBe(instance.rootMenu); expect(instance.levelTwoMenu.parentMenu).toBe(instance.levelOneMenu); - }); + })); - it('should pass the layout direction the nested menus', () => { + it('should pass the layout direction the nested menus', fakeAsync(() => { direction = 'rtl'; instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelTwoTrigger.openMenu(); fixture.detectChanges(); + tick(500); expect(instance.rootMenu.direction).toBe('rtl'); expect(instance.levelOneMenu.direction).toBe('rtl'); expect(instance.levelTwoMenu.direction).toBe('rtl'); - }); + })); - it('should emit an event when the hover state of the menu items changes', () => { + it('should emit an event when the hover state of the menu items changes', fakeAsync(() => { instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); const spy = jasmine.createSpy('hover spy'); const subscription = instance.rootMenu._hovered().subscribe(spy); @@ -1762,18 +1828,20 @@ describe('MatMenu', () => { dispatchMouseEvent(menuItems[0], 'mouseenter'); fixture.detectChanges(); + tick(500); expect(spy).toHaveBeenCalledTimes(1); dispatchMouseEvent(menuItems[1], 'mouseenter'); fixture.detectChanges(); + tick(500); expect(spy).toHaveBeenCalledTimes(2); subscription.unsubscribe(); - }); + })); - it('should toggle a nested menu when its trigger is hovered', async () => { + it('should toggle a nested menu when its trigger is hovered', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) @@ -1785,6 +1853,7 @@ describe('MatMenu', () => { dispatchMouseEvent(levelOneTrigger, 'mouseenter'); fixture.detectChanges(); + tick(); fixture.detectChanges(); expect(levelOneTrigger.classList) @@ -1796,7 +1865,7 @@ describe('MatMenu', () => { dispatchMouseEvent(items[items.indexOf(levelOneTrigger) + 1], 'mouseenter'); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') @@ -1805,9 +1874,9 @@ describe('MatMenu', () => { 'mat-mdc-menu-item-highlighted', 'Expected the trigger to not be highlighted', ); - }); + })); - it('should close all the open sub-menus when the hover state is changed at the root', async () => { + it('should close all the open sub-menus when the hover state is changed at the root', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -1816,10 +1885,12 @@ describe('MatMenu', () => { dispatchMouseEvent(levelOneTrigger, 'mouseenter'); fixture.detectChanges(); + tick(); const levelTwoTrigger = overlay.querySelector('#level-two-trigger')! as HTMLElement; dispatchMouseEvent(levelTwoTrigger, 'mouseenter'); fixture.detectChanges(); + tick(); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected three open menus') @@ -1827,23 +1898,23 @@ describe('MatMenu', () => { dispatchMouseEvent(items[items.indexOf(levelOneTrigger) + 1], 'mouseenter'); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); - }); + })); - it('should close submenu when hovering over disabled sibling item', async () => { + it('should close submenu when hovering over disabled sibling item', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); - await wait(200); + tick(500); const items = fixture.debugElement.queryAll(By.directive(MatMenuItem)); dispatchFakeEvent(items[0].nativeElement, 'mouseenter'); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') @@ -1856,16 +1927,17 @@ describe('MatMenu', () => { // Invoke the handler directly since the fake events are flaky on disabled elements. items[1].componentInstance._handleMouseEnter(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); - }); + })); - it('should not open submenu when hovering over disabled trigger', () => { + it('should not open submenu when hovering over disabled trigger', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') @@ -1880,15 +1952,17 @@ describe('MatMenu', () => { // Invoke the handler directly since the fake events are flaky on disabled elements. item.componentInstance._handleMouseEnter(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected to remain at one open menu') .toBe(1); - }); + })); - it('should open a nested menu when its trigger is clicked', () => { + it('should open a nested menu when its trigger is clicked', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); @@ -1897,6 +1971,7 @@ describe('MatMenu', () => { levelOneTrigger.click(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); @@ -1906,9 +1981,9 @@ describe('MatMenu', () => { expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected repeat clicks not to close the menu.') .toBe(2); - }); + })); - it('should open and close a nested menu with arrow keys in ltr', async () => { + it('should open and close a nested menu with arrow keys in ltr', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) @@ -1925,12 +2000,12 @@ describe('MatMenu', () => { expect(panels.length).withContext('Expected two open menus').toBe(2); dispatchKeyboardEvent(panels[1], 'keydown', LEFT_ARROW); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length).toBe(1); - }); + })); - it('should open and close a nested menu with the arrow keys in rtl', async () => { + it('should open and close a nested menu with the arrow keys in rtl', fakeAsync(() => { direction = 'rtl'; instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -1948,42 +2023,45 @@ describe('MatMenu', () => { expect(panels.length).withContext('Expected two open menus').toBe(2); dispatchKeyboardEvent(panels[1], 'keydown', RIGHT_ARROW); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length).toBe(1); - }); + })); - it('should not do anything with the arrow keys for a top-level menu', () => { + it('should not do anything with the arrow keys for a top-level menu', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); const menu = overlay.querySelector('.mat-mdc-menu-panel')!; dispatchKeyboardEvent(menu, 'keydown', RIGHT_ARROW); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one menu to remain open') .toBe(1); dispatchKeyboardEvent(menu, 'keydown', LEFT_ARROW); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one menu to remain open') .toBe(1); - }); + })); - it('should close all of the menus when the backdrop is clicked', async () => { + it('should close all of the menus when the backdrop is clicked', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); - await wait(200); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); instance.levelTwoTrigger.openMenu(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected three open menus') @@ -1997,16 +2075,17 @@ describe('MatMenu', () => { (overlay.querySelector('.cdk-overlay-backdrop')! as HTMLElement).click(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected no open menus') .toBe(0); - }); + })); - it('should shift focus between the sub-menus', () => { + it('should shift focus between the sub-menus', fakeAsync(() => { instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); expect(overlay.querySelector('.mat-mdc-menu-panel')!.contains(document.activeElement)) .withContext('Expected focus to be inside the root menu') @@ -2014,6 +2093,7 @@ describe('MatMenu', () => { instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel')[1].contains(document.activeElement)) .withContext('Expected focus to be inside the first nested menu') @@ -2021,6 +2101,7 @@ describe('MatMenu', () => { instance.levelTwoTrigger.openMenu(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel')[2].contains(document.activeElement)) .withContext('Expected focus to be inside the second nested menu') @@ -2028,6 +2109,7 @@ describe('MatMenu', () => { instance.levelTwoTrigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel')[1].contains(document.activeElement)) .withContext('Expected focus to be back inside the first nested menu') @@ -2035,13 +2117,14 @@ describe('MatMenu', () => { instance.levelOneTrigger.closeMenu(); fixture.detectChanges(); + tick(500); expect(overlay.querySelector('.mat-mdc-menu-panel')!.contains(document.activeElement)) .withContext('Expected focus to be back inside the root menu') .toBe(true); - }); + })); - it('should restore focus to a nested trigger when navigating via the keyboard', () => { + it('should restore focus to a nested trigger when navigating via the keyboard', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -2056,79 +2139,88 @@ describe('MatMenu', () => { LEFT_ARROW, ); fixture.detectChanges(); + tick(500); expect(spy).toHaveBeenCalled(); - }); + })); - it('should position the sub-menu to the right edge of the trigger in ltr', () => { + it('should position the sub-menu to the right edge of the trigger in ltr', fakeAsync(() => { instance.rootTriggerEl.nativeElement.style.position = 'fixed'; instance.rootTriggerEl.nativeElement.style.left = '50px'; instance.rootTriggerEl.nativeElement.style.top = '200px'; instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect(); const panelRect = overlay.querySelectorAll('.mat-mdc-menu-panel')[1].getBoundingClientRect(); expect(Math.round(triggerRect.right)).toBe(Math.round(panelRect.left)); expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING); - }); + })); - it('should fall back to aligning to the left edge of the trigger in ltr', () => { + it('should fall back to aligning to the left edge of the trigger in ltr', fakeAsync(() => { instance.rootTriggerEl.nativeElement.style.position = 'fixed'; instance.rootTriggerEl.nativeElement.style.right = '10px'; instance.rootTriggerEl.nativeElement.style.top = '200px'; instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect(); const panelRect = overlay.querySelectorAll('.mat-mdc-menu-panel')[1].getBoundingClientRect(); expect(Math.round(triggerRect.left)).toBe(Math.round(panelRect.right)); expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING); - }); + })); - it('should position the sub-menu to the left edge of the trigger in rtl', () => { + it('should position the sub-menu to the left edge of the trigger in rtl', fakeAsync(() => { direction = 'rtl'; instance.rootTriggerEl.nativeElement.style.position = 'fixed'; instance.rootTriggerEl.nativeElement.style.left = '50%'; instance.rootTriggerEl.nativeElement.style.top = '200px'; instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect(); const panelRect = overlay.querySelectorAll('.mat-mdc-menu-panel')[1].getBoundingClientRect(); expect(Math.round(triggerRect.left)).toBe(Math.round(panelRect.right)); expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING); - }); + })); - it('should fall back to aligning to the right edge of the trigger in rtl', () => { + it('should fall back to aligning to the right edge of the trigger in rtl', fakeAsync(() => { direction = 'rtl'; instance.rootTriggerEl.nativeElement.style.position = 'fixed'; instance.rootTriggerEl.nativeElement.style.left = '10px'; instance.rootTriggerEl.nativeElement.style.top = '200px'; instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); const triggerRect = overlay.querySelector('#level-one-trigger')!.getBoundingClientRect(); const panelRect = overlay.querySelectorAll('.mat-mdc-menu-panel')[1].getBoundingClientRect(); expect(Math.round(triggerRect.right)).toBe(Math.round(panelRect.left)); expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + MENU_PANEL_TOP_PADDING); - }); + })); it('should account for custom padding when offsetting the sub-menu', () => { instance.rootTriggerEl.nativeElement.style.position = 'fixed'; @@ -2149,7 +2241,7 @@ describe('MatMenu', () => { expect(Math.round(triggerRect.top)).toBe(Math.round(panelRect.top) + 15); }); - it('should close all of the menus when an item is clicked', async () => { + it('should close all of the menus when an item is clicked', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -2165,14 +2257,14 @@ describe('MatMenu', () => { (menus[2].querySelector('.mat-mdc-menu-item')! as HTMLElement).click(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected no open menus') .toBe(0); - }); + })); - it('should close all of the menus when the user tabs away', async () => { + it('should close all of the menus when the user tabs away', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -2188,16 +2280,17 @@ describe('MatMenu', () => { dispatchKeyboardEvent(menus[menus.length - 1], 'keydown', TAB); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected no open menus') .toBe(0); - }); + })); - it('should set a class on the menu items that trigger a sub-menu', () => { + it('should set a class on the menu items that trigger a sub-menu', fakeAsync(() => { instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); const menuItems = overlay.querySelectorAll('[mat-menu-item]'); @@ -2211,24 +2304,26 @@ describe('MatMenu', () => { expect(menuItems[0].classList).not.toContain('mat-mdc-menu-item-submenu-trigger'); expect(menuItems[0].querySelector('.mat-mdc-menu-submenu-icon')).toBeFalsy(); - }); + })); - it('should not change focus origin if origin not specified for trigger', () => { + it('should not change focus origin if origin not specified for trigger', fakeAsync(() => { instance.levelOneTrigger.openMenu(); fixture.detectChanges(); + tick(500); instance.levelOneTrigger.focus('mouse'); fixture.detectChanges(); instance.levelTwoTrigger.focus(); fixture.detectChanges(); + tick(500); const levelTwoTrigger = overlay.querySelector('#level-two-trigger')! as HTMLElement; expect(levelTwoTrigger.classList).toContain('cdk-focused'); expect(levelTwoTrigger.classList).toContain('cdk-mouse-focused'); - }); + })); - it('should close all of the menus when the root is closed programmatically', async () => { + it('should close all of the menus when the root is closed programmatically', fakeAsync(() => { instance.rootTrigger.openMenu(); fixture.detectChanges(); @@ -2244,16 +2339,17 @@ describe('MatMenu', () => { instance.rootTrigger.closeMenu(); fixture.detectChanges(); - await wait(200); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected no open menus') .toBe(0); - }); + })); - it('should toggle a nested menu when its trigger is added after init', () => { + it('should toggle a nested menu when its trigger is added after init', fakeAsync(() => { instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); @@ -2266,7 +2362,9 @@ describe('MatMenu', () => { dispatchMouseEvent(lazyTrigger, 'mouseenter'); fixture.detectChanges(); + tick(500); fixture.detectChanges(); + flush(); expect(lazyTrigger.classList) .withContext('Expected the trigger to be highlighted') @@ -2274,11 +2372,12 @@ describe('MatMenu', () => { expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); - }); + })); - it('should prevent the default mousedown action if the menu item opens a sub-menu', () => { + it('should prevent the default mousedown action if the menu item opens a sub-menu', fakeAsync(() => { instance.rootTrigger.openMenu(); fixture.detectChanges(); + tick(500); const event = createMouseEvent('mousedown'); Object.defineProperty(event, 'buttons', {get: () => 1}); @@ -2286,11 +2385,12 @@ describe('MatMenu', () => { dispatchEvent(overlay.querySelector('[mat-menu-item]')!, event); fixture.detectChanges(); + tick(500); expect(event.preventDefault).toHaveBeenCalled(); - }); + })); - it('should handle the items being rendered in a repeater', () => { + it('should handle the items being rendered in a repeater', fakeAsync(() => { const repeaterFixture = TestBed.createComponent(NestedMenuRepeater); overlay = overlayContainerElement; @@ -2298,24 +2398,27 @@ describe('MatMenu', () => { repeaterFixture.componentInstance.rootTriggerEl.nativeElement.click(); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); dispatchMouseEvent(overlay.querySelector('.level-one-trigger')!, 'mouseenter'); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); - }); + })); - it('should be able to trigger the same nested menu from different triggers', () => { + it('should be able to trigger the same nested menu from different triggers', fakeAsync(() => { const repeaterFixture = TestBed.createComponent(NestedMenuRepeater); overlay = overlayContainerElement; repeaterFixture.detectChanges(); repeaterFixture.componentInstance.rootTriggerEl.nativeElement.click(); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); @@ -2324,25 +2427,28 @@ describe('MatMenu', () => { dispatchMouseEvent(triggers[0], 'mouseenter'); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); dispatchMouseEvent(triggers[1], 'mouseenter'); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); - }); + })); - it('should close the initial menu if the user moves away while animating', () => { + it('should close the initial menu if the user moves away while animating', fakeAsync(() => { const repeaterFixture = TestBed.createComponent(NestedMenuRepeater); overlay = overlayContainerElement; repeaterFixture.detectChanges(); repeaterFixture.componentInstance.rootTriggerEl.nativeElement.click(); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); @@ -2351,53 +2457,59 @@ describe('MatMenu', () => { dispatchMouseEvent(triggers[0], 'mouseenter'); repeaterFixture.detectChanges(); + tick(100); dispatchMouseEvent(triggers[1], 'mouseenter'); repeaterFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); - }); + })); it( 'should be able to open a submenu through an item that is not a direct descendant ' + 'of the panel', - () => { + fakeAsync(() => { const nestedFixture = TestBed.createComponent(SubmenuDeclaredInsideParentMenu); overlay = overlayContainerElement; nestedFixture.detectChanges(); nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); nestedFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); dispatchMouseEvent(overlay.querySelector('.level-one-trigger')!, 'mouseenter'); nestedFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); - }, + }), ); it( 'should not close when hovering over a menu item inside a sub-menu panel that is declared' + 'inside the root menu', - () => { + fakeAsync(() => { const nestedFixture = TestBed.createComponent(SubmenuDeclaredInsideParentMenu); overlay = overlayContainerElement; nestedFixture.detectChanges(); nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); nestedFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected one open menu') .toBe(1); dispatchMouseEvent(overlay.querySelector('.level-one-trigger')!, 'mouseenter'); nestedFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') @@ -2405,14 +2517,15 @@ describe('MatMenu', () => { dispatchMouseEvent(overlay.querySelector('.level-two-item')!, 'mouseenter'); nestedFixture.detectChanges(); + tick(500); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus to remain') .toBe(2); - }, + }), ); - it('should preserve focus on a child menu trigger when hovering another trigger', () => { + it('should preserve focus on a child menu trigger when hovering another trigger', fakeAsync(() => { dispatchFakeEvent(instance.rootTriggerEl.nativeElement, 'mousedown'); instance.rootTriggerEl.nativeElement.click(); fixture.detectChanges(); @@ -2422,25 +2535,28 @@ describe('MatMenu', () => { dispatchMouseEvent(levelOneTrigger, 'mouseenter'); fixture.detectChanges(); + tick(); expect(overlay.querySelectorAll('.mat-mdc-menu-panel').length) .withContext('Expected two open menus') .toBe(2); dispatchMouseEvent(items[items.indexOf(levelOneTrigger) + 1], 'mouseenter'); fixture.detectChanges(); + tick(500); expect(document.activeElement).toBe( levelOneTrigger, 'Expected focus not to be returned to the initial trigger.', ); - }); + })); }); - it('should have a focus indicator', () => { + it('should have a focus indicator', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); + tick(500); const menuItemNativeElements = Array.from( overlayContainerElement.querySelectorAll('.mat-mdc-menu-item'), ); @@ -2448,11 +2564,11 @@ describe('MatMenu', () => { expect( menuItemNativeElements.every(element => element.classList.contains('mat-focus-indicator')), ).toBe(true); - }); + })); }); describe('MatMenu default overrides', () => { - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ { @@ -2462,9 +2578,9 @@ describe('MatMenu default overrides', () => { {provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}, ], }); - }); + })); - it('should allow for the default menu options to be overridden', () => { + it('should allow for the default menu options to be overridden', fakeAsync(() => { const fixture = TestBed.createComponent(SimpleMenu); fixture.detectChanges(); const menu = fixture.componentInstance.menu; @@ -2472,7 +2588,7 @@ describe('MatMenu default overrides', () => { expect(menu.overlapTrigger).toBe(true); expect(menu.xPosition).toBe('before'); expect(menu.yPosition).toBe('above'); - }); + })); }); const SIMPLE_MENU_TEMPLATE = ` @@ -2507,15 +2623,12 @@ const SIMPLE_MENU_TEMPLATE = ` @Component({ selector: 'mat-icon', template: '', - changeDetection: ChangeDetectionStrategy.Eager, }) class FakeIcon {} @Component({ template: SIMPLE_MENU_TEMPLATE, imports: [MatMenuTrigger, MatMenu, MatMenuItem, FakeIcon], - selector: 'simple-menu', - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMenu { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2534,8 +2647,8 @@ class SimpleMenu { @Component({ template: SIMPLE_MENU_TEMPLATE, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuTrigger, MatMenu, MatMenuItem, FakeIcon], - selector: 'simple-menu-on-push', }) class SimpleMenuOnPush extends SimpleMenu {} @@ -2548,7 +2661,6 @@ class SimpleMenuOnPush extends SimpleMenu {} `, imports: [MatButton, MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class DisabledMenu { @ViewChild('triggerEl', {read: ElementRef}) triggerEl!: ElementRef; @@ -2562,7 +2674,6 @@ class DisabledMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class PositionedMenu { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2583,7 +2694,6 @@ interface TestableMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class OverlapMenu implements TestableMenu { @Input() overlapTrigger: boolean = false; @@ -2600,7 +2710,6 @@ class OverlapMenu implements TestableMenu { `, exportAs: 'matCustomMenu', - changeDetection: ChangeDetectionStrategy.Eager, }) class CustomMenuPanel implements MatMenuPanel { direction!: Direction; @@ -2624,7 +2733,6 @@ class CustomMenuPanel implements MatMenuPanel { `, imports: [MatMenuTrigger, MatMenuItem, CustomMenuPanel], - changeDetection: ChangeDetectionStrategy.Eager, }) class CustomMenu { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2678,7 +2786,6 @@ class CustomMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMenu { @ViewChild('root') rootMenu!: MatMenu; @@ -2721,7 +2828,6 @@ class NestedMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMenuRepeater { @ViewChild('rootTriggerEl') rootTriggerEl!: ElementRef; @@ -2743,7 +2849,6 @@ class NestedMenuRepeater { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class SubmenuDeclaredInsideParentMenu { @ViewChild('rootTriggerEl') rootTriggerEl!: ElementRef; @@ -2773,7 +2878,6 @@ class DestroyChecker implements OnDestroy { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem, MatMenuContent, DestroyChecker], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleLazyMenu { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2801,7 +2905,6 @@ class SimpleLazyMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem, MatMenuContent], - changeDetection: ChangeDetectionStrategy.Eager, }) class LazyMenuWithContext { @ViewChild('triggerOne') triggerOne!: MatMenuTrigger; @@ -2820,7 +2923,6 @@ class LazyMenuWithContext { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class DynamicPanelMenu { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2838,7 +2940,6 @@ class DynamicPanelMenu { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithCheckboxItems { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2854,7 +2955,6 @@ class MenuWithCheckboxItems { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMenuWithRepeater { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2878,7 +2978,6 @@ class SimpleMenuWithRepeater { `, imports: [MatMenuTrigger, MatMenu, MatMenuItem, MatMenuContent], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMenuWithRepeaterInLazyContent { @ViewChild(MatMenuTrigger) trigger!: MatMenuTrigger; @@ -2905,6 +3004,7 @@ class SimpleMenuWithRepeaterInLazyContent { `, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuTrigger, MatMenu, MatMenuItem, MatMenuContent], }) class LazyMenuWithOnPush { @@ -2919,28 +3019,24 @@ class LazyMenuWithOnPush { `, imports: [MatMenuTrigger, MatMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class InvalidRecursiveMenu {} @Component({ template: '', imports: [MatMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class StaticAriaLabelMenu {} @Component({ template: '', imports: [MatMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class StaticAriaLabelledByMenu {} @Component({ template: '', imports: [MatMenu], - changeDetection: ChangeDetectionStrategy.Eager, }) class StaticAriaDescribedbyMenu {} @@ -2954,7 +3050,6 @@ class StaticAriaDescribedbyMenu {} `, imports: [MatMenuTrigger, MatMenu, MatMenuItem], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuWithRepeatedItems { @ViewChild(MatMenuTrigger, {static: false}) trigger!: MatMenuTrigger; diff --git a/src/material/menu/menu.ts b/src/material/menu/menu.ts index 9a76162a8611..8f6810fc7a12 100644 --- a/src/material/menu/menu.ts +++ b/src/material/menu/menu.ts @@ -8,6 +8,7 @@ import { AfterContentInit, + ChangeDetectionStrategy, Component, ContentChild, ContentChildren, @@ -97,6 +98,7 @@ const EXIT_ANIMATION = '_mat-menu-exit'; selector: 'mat-menu', templateUrl: 'menu.html', styleUrl: 'menu.css', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, exportAs: 'matMenu', host: { @@ -219,9 +221,6 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI * @param classes list of class names */ @Input('class') - get panelClass(): string { - return this._previousPanelClass; - } set panelClass(classes: string) { const previousPanelClass = this._previousPanelClass; const newClassList = {...this._classList}; @@ -244,7 +243,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI this._classList = newClassList; } - private _previousPanelClass: string = ''; + private _previousPanelClass!: string; /** * This method takes classes set on the host mat-menu element and applies them on the @@ -273,6 +272,8 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI readonly panelId: string = inject(_IdGenerator).getId('mat-menu-panel-'); + constructor(...args: unknown[]); + constructor() { const defaultOptions = inject(MAT_MENU_DEFAULT_OPTIONS); this.overlayPanelClass = defaultOptions.overlayPanelClass || ''; diff --git a/src/material/menu/testing/context-menu-harness.spec.ts b/src/material/menu/testing/context-menu-harness.spec.ts index 71c39eb089e4..77333990330f 100644 --- a/src/material/menu/testing/context-menu-harness.spec.ts +++ b/src/material/menu/testing/context-menu-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -76,7 +76,6 @@ describe('MatContextMenuHarness', () => { outline: solid 1px; } `, - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuHarnessTest { disabled = signal(false); diff --git a/src/material/menu/testing/menu-harness.spec.ts b/src/material/menu/testing/menu-harness.spec.ts index 55ac3068b5ed..84aa1721d16c 100644 --- a/src/material/menu/testing/menu-harness.spec.ts +++ b/src/material/menu/testing/menu-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {MatIconModule} from '@angular/material/icon'; @@ -165,7 +165,6 @@ describe('MatMenuHarness', () => { `, imports: [MatMenuModule, MatIconModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MenuHarnessTest {} @@ -192,7 +191,6 @@ class MenuHarnessTest {} `, imports: [MatMenuModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMenuHarnessTest { lastClickedLeaf = 0; diff --git a/src/material/paginator/paginator-intl.ts b/src/material/paginator/paginator-intl.ts index 09bf620b3d76..1b43ee47a647 100644 --- a/src/material/paginator/paginator-intl.ts +++ b/src/material/paginator/paginator-intl.ts @@ -6,14 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {Subject} from 'rxjs'; /** * To modify the labels and text displayed, create a new instance of MatPaginatorIntl and * include it in a custom provider */ -@Service() +@Injectable({providedIn: 'root'}) export class MatPaginatorIntl { /** * Stream to emit from when labels are changed. Use this to notify components when the labels have diff --git a/src/material/paginator/paginator.spec.ts b/src/material/paginator/paginator.spec.ts index 4fa53ae27350..198543a45c85 100644 --- a/src/material/paginator/paginator.spec.ts +++ b/src/material/paginator/paginator.spec.ts @@ -1,12 +1,4 @@ -import { - ChangeDetectorRef, - Component, - Provider, - Type, - ViewChild, - inject, - ChangeDetectionStrategy, -} from '@angular/core'; +import {ChangeDetectorRef, Component, Provider, Type, ViewChild, inject} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {ThemePalette} from '../core'; import {MatSelect} from '../select'; @@ -654,7 +646,6 @@ function getLastButton(fixture: ComponentFixture): HTMLButtonElement { `, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorApp { pageIndex = 0; @@ -681,7 +672,6 @@ class MatPaginatorApp { @Component({ template: ``, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorWithoutInputsApp { @ViewChild(MatPaginator) paginator!: MatPaginator; @@ -690,7 +680,6 @@ class MatPaginatorWithoutInputsApp { @Component({ template: ``, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorWithoutPageSizeApp { @ViewChild(MatPaginator) paginator!: MatPaginator; @@ -699,7 +688,6 @@ class MatPaginatorWithoutPageSizeApp { @Component({ template: ``, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorWithoutOptionsApp { @ViewChild(MatPaginator) paginator!: MatPaginator; @@ -714,7 +702,6 @@ class MatPaginatorWithoutOptionsApp { `, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorWithStringValues { @ViewChild(MatPaginator) paginator!: MatPaginator; @@ -723,7 +710,6 @@ class MatPaginatorWithStringValues { @Component({ template: ``, imports: [MatPaginator], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatPaginatorWithReadonlyOptions { @ViewChild(MatPaginator) paginator!: MatPaginator; diff --git a/src/material/paginator/paginator.ts b/src/material/paginator/paginator.ts index 4bb81e71b631..f4f910f0167e 100644 --- a/src/material/paginator/paginator.ts +++ b/src/material/paginator/paginator.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, @@ -103,6 +104,7 @@ export const MAT_PAGINATOR_DEFAULT_OPTIONS = new InjectionToken = this._initializedStream; /** Inserted by Angular inject() migration for backwards compatibility */ + constructor(...args: unknown[]); + constructor() { const _intl = this._intl; const defaults = inject(MAT_PAGINATOR_DEFAULT_OPTIONS, { diff --git a/src/material/paginator/testing/paginator-harness.spec.ts b/src/material/paginator/testing/paginator-harness.spec.ts index ec036e154bc5..c298481e2225 100644 --- a/src/material/paginator/testing/paginator-harness.spec.ts +++ b/src/material/paginator/testing/paginator-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -133,7 +133,6 @@ describe('MatPaginatorHarness', () => { `, imports: [MatPaginatorModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class PaginatorHarnessTest { length = 500; diff --git a/src/material/progress-bar/progress-bar.spec.ts b/src/material/progress-bar/progress-bar.spec.ts index 550691fee044..93055cc3da8c 100644 --- a/src/material/progress-bar/progress-bar.spec.ts +++ b/src/material/progress-bar/progress-bar.spec.ts @@ -6,7 +6,6 @@ import { EnvironmentProviders, Provider, Type, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -334,13 +333,11 @@ describe('MatProgressBar', () => { @Component({ template: '', imports: [MatProgressBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class BasicProgressBar {} @Component({ template: '', imports: [MatProgressBar], - changeDetection: ChangeDetectionStrategy.Eager, }) class BufferProgressBar {} diff --git a/src/material/progress-bar/progress-bar.ts b/src/material/progress-bar/progress-bar.ts index afc75ebadc85..cb086420c588 100644 --- a/src/material/progress-bar/progress-bar.ts +++ b/src/material/progress-bar/progress-bar.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ViewEncapsulation, ElementRef, @@ -103,6 +104,7 @@ export type ProgressBarMode = 'determinate' | 'indeterminate' | 'buffer' | 'quer }, templateUrl: 'progress-bar.html', styleUrl: 'progress-bar.css', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MatProgressBar implements AfterViewInit, OnDestroy { @@ -112,6 +114,8 @@ export class MatProgressBar implements AfterViewInit, OnDestroy { private _renderer = inject(Renderer2); private _cleanupTransitionEnd: (() => void) | undefined; + constructor(...args: unknown[]); + constructor() { const animationsState = _getAnimationsState(); diff --git a/src/material/progress-bar/testing/progress-bar-harness.spec.ts b/src/material/progress-bar/testing/progress-bar-harness.spec.ts index 53072b9fdcab..a29643241493 100644 --- a/src/material/progress-bar/testing/progress-bar-harness.spec.ts +++ b/src/material/progress-bar/testing/progress-bar-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -41,7 +41,6 @@ describe('MatProgressBarHarness', () => { `, imports: [MatProgressBarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressBarHarnessTest { value = signal(undefined); diff --git a/src/material/progress-spinner/progress-spinner.spec.ts b/src/material/progress-spinner/progress-spinner.spec.ts index 9632e559ecd6..71b995aa057c 100644 --- a/src/material/progress-spinner/progress-spinner.spec.ts +++ b/src/material/progress-spinner/progress-spinner.spec.ts @@ -1,6 +1,6 @@ import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {MatProgressSpinnerModule} from './progress-spinner-module'; import { MatProgressSpinner, @@ -381,14 +381,12 @@ describe('MatProgressSpinner', () => { @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BasicProgressSpinner {} @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerCustomStrokeWidth { strokeWidth!: number; @@ -397,7 +395,6 @@ class ProgressSpinnerCustomStrokeWidth { @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerCustomDiameter { diameter!: number; @@ -406,14 +403,12 @@ class ProgressSpinnerCustomDiameter { @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class IndeterminateProgressSpinner {} @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerWithValueAndBoundMode { mode = signal('indeterminate'); @@ -424,7 +419,6 @@ class ProgressSpinnerWithValueAndBoundMode { template: ` `, imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SpinnerWithColor { color = signal('primary'); @@ -434,7 +428,6 @@ class SpinnerWithColor { template: ` `, imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerWithColor { color = signal('primary'); @@ -445,13 +438,11 @@ class ProgressSpinnerWithColor { `, imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerWithStringValues {} @Component({ template: '', imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SpinnerWithMode {} diff --git a/src/material/progress-spinner/progress-spinner.ts b/src/material/progress-spinner/progress-spinner.ts index d8a8c02eb74f..d8c48f828a91 100644 --- a/src/material/progress-spinner/progress-spinner.ts +++ b/src/material/progress-spinner/progress-spinner.ts @@ -7,6 +7,7 @@ */ import { + ChangeDetectionStrategy, Component, ElementRef, InjectionToken, @@ -83,6 +84,7 @@ const BASE_STROKE_WIDTH = 10; }, templateUrl: 'progress-spinner.html', styleUrl: 'progress-spinner.css', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet], }) @@ -113,6 +115,8 @@ export class MatProgressSpinner { /** The element of the determinate spinner. */ @ViewChild('determinateSpinner') _determinateCircle!: ElementRef; + constructor(...args: unknown[]); + constructor() { const defaults = inject(MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS); const animationsState = _getAnimationsState(); diff --git a/src/material/progress-spinner/testing/progress-spinner-harness.spec.ts b/src/material/progress-spinner/testing/progress-spinner-harness.spec.ts index a21dab13764a..e3f60c47d8fa 100644 --- a/src/material/progress-spinner/testing/progress-spinner-harness.spec.ts +++ b/src/material/progress-spinner/testing/progress-spinner-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -45,7 +45,6 @@ describe('MatProgressSpinnerHarness', () => { `, imports: [MatProgressSpinnerModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ProgressSpinnerHarnessTest { value = signal(0); diff --git a/src/material/radio/_radio-common.scss b/src/material/radio/_radio-common.scss index b73b0382d591..517a2f3cc734 100644 --- a/src/material/radio/_radio-common.scss +++ b/src/material/radio/_radio-common.scss @@ -23,6 +23,7 @@ $fallbacks: m3-radio.get-tokens(); box-sizing: content-box; width: $_icon-size; height: $_icon-size; + cursor: pointer; // This is something we inherited from MDC, but it shouldn't be necessary. // Removing it will likely lead to screenshot diffs. @@ -32,8 +33,6 @@ $fallbacks: m3-radio.get-tokens(); padding: calc((#{$size-token} - #{$_icon-size}) / 2); @if ($is-interactive) { - cursor: pointer; - // MDC's hover indication comes from their ripple which we don't use. &:hover > .mdc-radio__native-control:not([disabled]):not(:focus) { & ~ .mdc-radio__background::before { diff --git a/src/material/radio/radio.html b/src/material/radio/radio.html index f7546b17e11e..cdcdbc436cc1 100644 --- a/src/material/radio/radio.html +++ b/src/material/radio/radio.html @@ -1,7 +1,7 @@ -
    `, imports: [MatSortModule, MatTableModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortableMissingIdApp {} @@ -613,7 +607,6 @@ class MatSortableMissingIdApp {}
    `, imports: [MatSortModule, MatTableModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortableInvalidDirection {} @@ -629,7 +622,6 @@ class MatSortableInvalidDirection {}
    `, imports: [MatSortModule, MatTableModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortWithoutExplicitInputs { elementRef = inject>(ElementRef); @@ -662,7 +654,6 @@ class MatSortWithoutExplicitInputs {
    `, imports: [MatSortModule, MatTableModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortWithArrowPosition { arrowPosition!: 'before' | 'after'; @@ -683,7 +674,6 @@ class MatSortWithArrowPosition {
    `, imports: [MatSortModule, MatTableModule, CdkTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortWithoutInputs { @ViewChild(MatSort) matSort!: MatSort; diff --git a/src/material/sort/testing/sort-harness.spec.ts b/src/material/sort/testing/sort-harness.spec.ts index e019f372358f..ab36834c920f 100644 --- a/src/material/sort/testing/sort-harness.spec.ts +++ b/src/material/sort/testing/sort-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -133,7 +133,6 @@ describe('MatSortHarness', () => { `, imports: [MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SortHarnessTest { disableThirdHeader = signal(false); diff --git a/src/material/stepper/_m2-stepper.scss b/src/material/stepper/_m2-stepper.scss index fb5df6095a74..c2fb62f9faff 100644 --- a/src/material/stepper/_m2-stepper.scss +++ b/src/material/stepper/_m2-stepper.scss @@ -9,8 +9,8 @@ @return ( base: ( - stepper-header-focus-state-layer-shape: 4px, - stepper-header-hover-state-layer-shape: 4px, + stepper-header-focus-state-layer-shape: 0, + stepper-header-hover-state-layer-shape: 0, ), color: map.merge(private-get-color-palette-color-tokens($theme, primary), ( stepper-container-color: map.get($system, surface), diff --git a/src/material/stepper/step-content.ts b/src/material/stepper/step-content.ts index 21b660a91cc9..569f22e23fb3 100644 --- a/src/material/stepper/step-content.ts +++ b/src/material/stepper/step-content.ts @@ -16,4 +16,7 @@ import {Directive, TemplateRef, inject} from '@angular/core'; }) export class MatStepContent { _template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/material/stepper/step-header.scss b/src/material/stepper/step-header.scss index 2dad0d3a6d7b..9ea7bf14836d 100644 --- a/src/material/stepper/step-header.scss +++ b/src/material/stepper/step-header.scss @@ -29,20 +29,12 @@ $fallbacks: m3-stepper.get-tokens(); &:hover[aria-disabled='false'] { background-color: token-utils.slot(stepper-header-hover-state-layer-color, $fallbacks); border-radius: token-utils.slot(stepper-header-hover-state-layer-shape, $fallbacks); - - .mat-step-header-ripple::before { - border-radius: token-utils.slot(stepper-header-hover-state-layer-shape, $fallbacks); - } } &.cdk-keyboard-focused, &.cdk-program-focused { background-color: token-utils.slot(stepper-header-focus-state-layer-color, $fallbacks); border-radius: token-utils.slot(stepper-header-focus-state-layer-shape, $fallbacks); - - .mat-step-header-ripple::before { - border-radius: token-utils.slot(stepper-header-focus-state-layer-shape, $fallbacks); - } } // On touch devices the :hover state will linger on the element after a tap. diff --git a/src/material/stepper/step-header.ts b/src/material/stepper/step-header.ts index d6a39d4dfc56..64dd1a54d329 100644 --- a/src/material/stepper/step-header.ts +++ b/src/material/stepper/step-header.ts @@ -8,6 +8,7 @@ import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, @@ -38,6 +39,7 @@ import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/privat 'role': '', // ignore cdk role in favor of setting appropriately in html }, encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatRipple, NgTemplateOutlet, MatIcon], }) export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDestroy { @@ -82,6 +84,8 @@ export class MatStepHeader extends CdkStepHeader implements AfterViewInit, OnDes */ @Input() color!: ThemePalette; + constructor(...args: unknown[]); + constructor() { super(); diff --git a/src/material/stepper/stepper-icon.ts b/src/material/stepper/stepper-icon.ts index 3f55c8829d6c..b392cf6f16d1 100644 --- a/src/material/stepper/stepper-icon.ts +++ b/src/material/stepper/stepper-icon.ts @@ -30,4 +30,7 @@ export class MatStepperIcon { /** Name of the icon to be overridden. */ @Input('matStepperIcon') name!: StepState; + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/material/stepper/stepper-intl.ts b/src/material/stepper/stepper-intl.ts index 2970d1b226be..30063db9f6d1 100644 --- a/src/material/stepper/stepper-intl.ts +++ b/src/material/stepper/stepper-intl.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Service} from '@angular/core'; +import {Injectable} from '@angular/core'; import {Subject} from 'rxjs'; /** Stepper data that is required for internationalization. */ -@Service() +@Injectable({providedIn: 'root'}) export class MatStepperIntl { /** * Stream that emits whenever the labels here are changed. Use this to notify diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index fbb58e7f27cb..1d281f98657f 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -55,9 +55,8 @@ class="mat-vertical-content-container" [class.mat-stepper-vertical-line]="!$last" [class.mat-vertical-content-container-active]="selectedIndex === $index" - [attr.inert]="selectedIndex === $index ? null : ''" - [attr.aria-label]="ariaLabel"> -
    +
    -
    + class="mat-horizontal-stepper-header-container" + role="tablist"> @for (step of steps; track step) { ``` - -#### Using Signal Forms -The stepper also supports passing in a Signal Forms field as the `stepControl`: - - - ### Types of steps #### Optional step @@ -159,17 +153,17 @@ However, it can be overridden the same way as mentioned above. In order to use the custom step states, you must add the `displayDefaultIndicatorType` option to the global default stepper options which can be specified by providing a value for -`STEPPER_GLOBAL_OPTIONS` in your app config. +`STEPPER_GLOBAL_OPTIONS` in your application's root module. ```ts -bootstrapApplication(MyApp, { +@NgModule({ providers: [ { provide: STEPPER_GLOBAL_OPTIONS, useValue: { displayDefaultIndicatorType: false } } ] -}); +}) ``` @@ -183,14 +177,14 @@ errors via the `showError` option in the `STEPPER_GLOBAL_OPTIONS` injection toke will not affect steppers marked as `linear`. ```ts -bootstrapApplication(MyApp, { +@NgModule({ providers: [ { provide: STEPPER_GLOBAL_OPTIONS, useValue: { showError: true } } ] -}); +}) ``` @@ -219,14 +213,16 @@ viewport. ### Localizing labels Labels used by the stepper are provided through `MatStepperIntl`. Localization of these messages -can be done by providing a subclass with translated values in your app config. +can be done by providing a subclass with translated values in your application root module. ```ts -bootstrapApplication(MyApp, { +@NgModule({ + imports: [MatStepperModule], providers: [ {provide: MatStepperIntl, useClass: MyIntl}, - ] -}); + ], +}) +export class MyApp {} ``` diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index cfb7b1f12d03..1f4bb9397a83 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -34,7 +34,6 @@ import { WritableSignal, inject, signal, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import { @@ -387,7 +386,7 @@ describe('MatStepper', () => { }); }); - describe('basic stepper when attempting to get the selected step too early', () => { + describe('basic stepper when attempting to set the selected step too early', () => { it('should not throw', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); const stepper: MatStepper = fixture.debugElement.query( @@ -966,7 +965,7 @@ describe('MatStepper', () => { expect(headerRipples.every(ripple => ripple.disabled)).toBe(true); }); - it('should be able to focus the step headers', () => { + it('should be able to disable ripples', () => { const fixture = createComponent(SimpleMatVerticalStepperApp); fixture.detectChanges(); @@ -1017,15 +1016,6 @@ describe('MatStepper', () => { expect(headers[2].classList.contains('mat-primary')).toBe(true); expect(headers[1].classList.contains('mat-accent')).toBe(true); }); - - it('should set an aria label on the content container', () => { - const fixture = createComponent(SimpleMatVerticalStepperApp); - fixture.componentInstance.ariaLabel.set('My Stepper'); - fixture.detectChanges(); - - const container = fixture.nativeElement.querySelector('.mat-vertical-content-container'); - expect(container.getAttribute('aria-label')).toBe('My Stepper'); - }); }); describe('horizontal stepper', () => { @@ -1226,17 +1216,6 @@ describe('MatStepper', () => { expect(stepperHost.classList).toContain('mat-stepper-header-position-bottom'); }); - - it('should set an aria-label on the header container', () => { - const fixture = createComponent(SimpleMatHorizontalStepperApp); - fixture.componentInstance.ariaLabel.set('My Stepper'); - fixture.detectChanges(); - - const container = fixture.nativeElement.querySelector( - '.mat-horizontal-stepper-header-container', - ); - expect(container.getAttribute('aria-label')).toBe('My Stepper'); - }); }); describe('linear stepper with valid step', () => { @@ -1853,7 +1832,6 @@ function createComponent( `, imports: [MatStepperModule, MatInputModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatHorizontalStepperWithErrorsApp { private readonly _formBuilder = inject(FormBuilder); @@ -1869,8 +1847,7 @@ class MatHorizontalStepperWithErrorsApp { + [headerPosition]="headerPosition()"> Step 1 Content 1 @@ -1897,12 +1874,10 @@ class MatHorizontalStepperWithErrorsApp { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatHorizontalStepperApp { @ViewChild(MatStepper) stepper!: MatStepper; inputLabel = 'Step 3'; - ariaLabel = signal(null); disableRipple = signal(false); stepperTheme = signal(undefined); secondStepTheme = signal(undefined); @@ -1911,7 +1886,7 @@ class SimpleMatHorizontalStepperApp { @Component({ template: ` - + Step 1 Content 1 @@ -1940,12 +1915,10 @@ class SimpleMatHorizontalStepperApp { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatVerticalStepperApp { @ViewChild(MatStepper) stepper!: MatStepper; inputLabel = signal('Step 3'); - ariaLabel = signal(null); showStepTwo = signal(true); disableRipple = signal(false); stepperTheme = signal(undefined); @@ -1991,7 +1964,6 @@ class SimpleMatVerticalStepperApp { `, imports: [ReactiveFormsModule, MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class LinearMatVerticalStepperApp { validationTrigger = new Subject(); @@ -2015,7 +1987,6 @@ class LinearMatVerticalStepperApp { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimplePreselectedMatHorizontalStepperApp { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2061,7 +2032,6 @@ class SimplePreselectedMatHorizontalStepperApp { `, imports: [ReactiveFormsModule, MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class LinearMatVerticalStepperAppForAlreadyFilledForm { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2087,7 +2057,6 @@ class LinearMatVerticalStepperAppForAlreadyFilledForm { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleStepperWithoutStepControl { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2110,7 +2079,6 @@ class SimpleStepperWithoutStepControl { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleStepperWithStepControlAndCompletedBinding { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2137,7 +2105,6 @@ class SimpleStepperWithStepControlAndCompletedBinding { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class IconOverridesStepper { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2176,7 +2143,6 @@ class IconOverridesStepper { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class IndirectDescendantIconOverridesStepper extends IconOverridesStepper {} @@ -2189,7 +2155,6 @@ class IndirectDescendantIconOverridesStepper extends IconOverridesStepper {} `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class LinearStepperWithValidOptionalStep { controls = [0, 0, 0].map(() => new FormControl('')); @@ -2203,7 +2168,6 @@ class LinearStepperWithValidOptionalStep { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithAriaInputs { ariaLabel = signal(''); @@ -2221,7 +2185,6 @@ class StepperWithAriaInputs { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithIndirectDescendantSteps { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2242,7 +2205,6 @@ class StepperWithIndirectDescendantSteps { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithNgIf { showStep2 = signal(false); @@ -2262,7 +2224,6 @@ class StepperWithNgIf { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedSteppers { @ViewChildren(MatStepper) steppers!: QueryList; @@ -2277,7 +2238,6 @@ class NestedSteppers { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithStaticOutOfBoundsIndex { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2301,7 +2261,6 @@ class StepperWithStaticOutOfBoundsIndex { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithLazyContent { selectedIndex = signal(0); @@ -2318,7 +2277,6 @@ class StepperWithLazyContent { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class HorizontalStepperWithDelayedStep { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2334,7 +2292,6 @@ class HorizontalStepperWithDelayedStep { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperWithTwoWayBindingOnSelectedIndex { index: number = 0; @@ -2353,7 +2310,6 @@ class StepperWithTwoWayBindingOnSelectedIndex { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class HorizontalStepperWithHeaderPrefix { @ViewChild(MatStepper) stepper!: MatStepper; @@ -2372,7 +2328,6 @@ class HorizontalStepperWithHeaderPrefix { `, imports: [MatStepperModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class VerticalStepperWithHeaderPrefix { @ViewChild(MatStepper) stepper!: MatStepper; diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index a22a9c1e554e..1189d3d9965c 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -10,6 +10,7 @@ import {CdkStep, CdkStepper} from '@angular/cdk/stepper'; import { AfterContentInit, AfterViewInit, + ChangeDetectionStrategy, Component, ContentChild, ContentChildren, @@ -41,7 +42,6 @@ import {MatStepHeader} from './step-header'; import {MatStepLabel} from './step-label'; import {MatStepperIcon, MatStepperIconContext} from './stepper-icon'; import {MatStepContent} from './step-content'; -import type {Field} from '@angular/forms/signals'; @Component({ selector: 'mat-step', @@ -52,6 +52,7 @@ import type {Field} from '@angular/forms/signals'; ], encapsulation: ViewEncapsulation.None, exportAs: 'matStep', + changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkPortalOutlet], host: { 'hidden': '', // Hide the steps so they don't affect the layout. @@ -113,12 +114,6 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI return originalErrorState || customErrorState; } - - isSignalErrorState(field: Field | null): boolean { - const originalErrorState = this._errorStateMatcher.isSignalErrorState?.(field) ?? false; - const customErrorState = !!(field && field().invalid() && this.interacted); - return originalErrorState || customErrorState; - } } @Component({ @@ -139,6 +134,7 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI }, providers: [{provide: CdkStepper, useExisting: MatStepper}], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, MatStepHeader], }) export class MatStepper extends CdkStepper implements AfterViewInit, AfterContentInit, OnDestroy { @@ -192,12 +188,6 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten @Input() headerPosition: 'top' | 'bottom' = 'top'; - /** - * ARIA label for the stepper. - */ - @Input('aria-label') - ariaLabel: string | null = null; - /** The content prefix to use in the stepper header. */ readonly headerPrefix = input | null>(null); @@ -210,19 +200,15 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten return this._animationDuration; } set animationDuration(value: string) { - if (/^[0-9]+(?:\.[0-9]+)?$/.test(value)) { - this._animationDuration = value + 'ms'; - } else if (/^[0-9]+(?:\.[0-9]+)?(?:ms|s)$/.test(value)) { - this._animationDuration = value; - } else { - this._animationDuration = ''; - } + this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value; } private _animationDuration = ''; /** Whether the stepper is rendering on the server. */ protected _isServer: boolean = !inject(Platform).isBrowser; + constructor(...args: unknown[]); + constructor() { super(); diff --git a/src/material/stepper/testing/stepper-harness.spec.ts b/src/material/stepper/testing/stepper-harness.spec.ts index 34b156e44aaf..8b34bcbec63c 100644 --- a/src/material/stepper/testing/stepper-harness.spec.ts +++ b/src/material/stepper/testing/stepper-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ReactiveFormsModule, FormGroup, FormControl, Validators} from '@angular/forms'; import {HarnessLoader, parallel} from '@angular/cdk/testing'; @@ -300,7 +300,6 @@ describe('MatStepperHarness', () => { `, imports: [MatStepperModule, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StepperHarnessTest { oneGroup = new FormGroup({ diff --git a/src/material/table/row.ts b/src/material/table/row.ts index 489432c6c29e..1c0ac4b234c3 100644 --- a/src/material/table/row.ts +++ b/src/material/table/row.ts @@ -80,7 +80,7 @@ export class MatRowDef extends CdkRowDef {} }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, exportAs: 'matHeaderRow', providers: [{provide: CdkHeaderRow, useExisting: MatHeaderRow}], @@ -98,7 +98,7 @@ export class MatHeaderRow extends CdkHeaderRow {} }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, exportAs: 'matFooterRow', providers: [{provide: CdkFooterRow, useExisting: MatFooterRow}], @@ -116,7 +116,7 @@ export class MatFooterRow extends CdkFooterRow {} }, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, exportAs: 'matRow', providers: [{provide: CdkRow, useExisting: MatRow}], diff --git a/src/material/table/table-data-source.spec.ts b/src/material/table/table-data-source.spec.ts index 774d2dd4bafe..feaf4a846e2a 100644 --- a/src/material/table/table-data-source.spec.ts +++ b/src/material/table/table-data-source.spec.ts @@ -1,7 +1,13 @@ import {MatTableDataSource} from './table-data-source'; import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import {MatSort, MatSortModule} from '@angular/material/sort'; -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; + +declare global { + interface Window { + ngDevMode?: object | null; + } +} describe('MatTableDataSource', () => { describe('sort', () => { @@ -89,7 +95,7 @@ describe('MatTableDataSource', () => { it('does not warn in non-dev mode when filtering non-object data', fakeAsync(() => { const warnSpy = spyOn(console, 'warn'); - (window as any).ngDevMode = null; + window.ngDevMode = null; dataSource.data = [1, 2, 3, 4, 5] as unknown as {'prop': number}[]; dataSource.filter = '1'; @@ -101,7 +107,7 @@ describe('MatTableDataSource', () => { it('displays the warning in dev mode when filtering non-object data', fakeAsync(() => { const warnSpy = spyOn(console, 'warn'); - (window as any).ngDevMode = {}; + window.ngDevMode = {}; dataSource.data = [1, 2, 3, 4, 5] as unknown as {'prop': number}[]; dataSource.filter = '1'; @@ -118,7 +124,6 @@ describe('MatTableDataSource', () => { @Component({ template: `
    `, imports: [MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatSortApp { @ViewChild(MatSort, {static: true}) sort!: MatSort; diff --git a/src/material/table/table.spec.ts b/src/material/table/table.spec.ts index fe8ba5725eaa..d8a7ef19d7bd 100644 --- a/src/material/table/table.spec.ts +++ b/src/material/table/table.spec.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, Component, OnInit, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing'; import {MatTable, MatTableDataSource, MatTableModule} from './index'; import {DataSource} from '@angular/cdk/table'; @@ -692,7 +692,6 @@ class FakeDataSource extends DataSource { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTableApp { dataSource = new FakeDataSource(); @@ -728,7 +727,6 @@ class MatTableApp { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NativeHtmlTableApp { dataSource = new FakeDataSource(); @@ -783,7 +781,6 @@ class NativeHtmlTableApp { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedTableApp { dataSource = new FakeDataSource(); @@ -803,7 +800,6 @@ class NestedTableApp { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class StickyTableApp { dataSource = new FakeDataSource(); @@ -832,7 +828,6 @@ class StickyTableApp { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTableWithWhenRowApp { multiTemplateDataRows = false; @@ -871,7 +866,6 @@ class MatTableWithWhenRowApp { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ArrayDataSourceMatTableApp implements AfterViewInit { underlyingDataSource = new FakeDataSource(); @@ -925,7 +919,6 @@ class ArrayDataSourceMatTableApp implements AfterViewInit { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTableWithSortApp implements OnInit { underlyingDataSource = new FakeDataSource(); @@ -978,7 +971,6 @@ class MatTableWithSortApp implements OnInit { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTableWithPaginatorApp implements OnInit { underlyingDataSource = new FakeDataSource(); @@ -1021,7 +1013,6 @@ class MatTableWithPaginatorApp implements OnInit { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TableWithNgContainerRow { dataSource = new FakeDataSource(); @@ -1060,7 +1051,6 @@ class TableWithNgContainerRow { `, imports: [MatTableModule, MatPaginatorModule, MatSortModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatFlexTableApp { dataSource = new FakeDataSource(); diff --git a/src/material/table/table.ts b/src/material/table/table.ts index 9951870a03ae..54099f334fd9 100644 --- a/src/material/table/table.ts +++ b/src/material/table/table.ts @@ -77,7 +77,7 @@ export class MatRecycleRows {} encapsulation: ViewEncapsulation.None, // See note on CdkTable for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [HeaderRowOutlet, DataRowOutlet, NoDataRowOutlet, FooterRowOutlet], }) export class MatTable extends CdkTable { diff --git a/src/material/table/testing/table-harness.spec.ts b/src/material/table/testing/table-harness.spec.ts index 8380c048c3ec..e0e3dd46516d 100644 --- a/src/material/table/testing/table-harness.spec.ts +++ b/src/material/table/testing/table-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -228,7 +228,6 @@ describe('MatTableHarness', () => { `, imports: [MatTableModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TableHarnessTest { displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; diff --git a/src/material/table/text-column.ts b/src/material/table/text-column.ts index b52eedc75a03..578ae9662606 100644 --- a/src/material/table/text-column.ts +++ b/src/material/table/text-column.ts @@ -38,7 +38,7 @@ import {MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell} from // mean's the template in the table's view will not have the updated value (and in fact will cause // an ExpressionChangedAfterItHasBeenCheckedError). // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell], }) export class MatTextColumn extends CdkTextColumn {} diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index e3fc7a550ed2..7523f6010dfd 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -60,8 +60,7 @@ $mat-tab-animation-duration: 500ms !default; } .mdc-tab-indicator__content { - transition: var(--mat-tab-header-animation-duration, 250ms) - transform cubic-bezier(0.4, 0, 0.2, 1); + transition: var(--mat-tab-animation-duration, 250ms) transform cubic-bezier(0.4, 0, 0.2, 1); transform-origin: left; opacity: 0; } @@ -248,7 +247,7 @@ $mat-tab-animation-duration: 500ms !default; } .mdc-tab-indicator .mdc-tab-indicator__content { - transition-duration: var(--mat-tab-header-animation-duration, 250ms); + transition-duration: var(--mat-tab-animation-duration, 250ms); } .mat-mdc-tab-header-pagination { diff --git a/src/material/tabs/paginated-tab-header.ts b/src/material/tabs/paginated-tab-header.ts index 6e6d3bf69c28..ffae76b49494 100644 --- a/src/material/tabs/paginated-tab-header.ts +++ b/src/material/tabs/paginated-tab-header.ts @@ -63,19 +63,6 @@ const HEADER_SCROLL_INTERVAL = 100; /** Item inside a paginated tab header. */ export type MatPaginatedTabHeaderItem = FocusableOption & {elementRef: ElementRef}; -/** Normalizes an animation duration value. */ -export function normalizeDuration(value: string | number): string { - const stringValue = value + ''; - - if (/^[0-9]+(?:\.[0-9]+)?$/.test(stringValue)) { - return `${value}ms`; - } else if (/^[0-9]+(?:\.[0-9]+)?(?:ms|s)$/.test(stringValue)) { - return stringValue; - } else { - return ''; - } -} - /** * Base class for a tab header that supported pagination. * @docs-private @@ -172,6 +159,8 @@ export abstract class MatPaginatedTabHeader /** Event emitted when a label is focused. */ @Output() readonly indexFocused: EventEmitter = new EventEmitter(); + constructor(...args: unknown[]); + constructor() { // Bind the `mouseleave` event on the outside since it doesn't change anything in the view. this._eventCleanups = this._ngZone.runOutsideAngular(() => [ diff --git a/src/material/tabs/tab-body.scss b/src/material/tabs/tab-body.scss index dd3dcf078a5a..89db99c45976 100644 --- a/src/material/tabs/tab-body.scss +++ b/src/material/tabs/tab-body.scss @@ -51,7 +51,7 @@ .mat-tab-body-content-can-animate { // Note: there's a 1ms delay so that transition events // still fire even if the duration is set to zero. - transition: transform var(--mat-tab-body-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1); + transition: transform var(--mat-tab-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1); .mat-mdc-tab-body-wrapper._mat-animation-noopable & { transition: none; diff --git a/src/material/tabs/tab-body.spec.ts b/src/material/tabs/tab-body.spec.ts index a2575259556c..e6df95651fef 100644 --- a/src/material/tabs/tab-body.spec.ts +++ b/src/material/tabs/tab-body.spec.ts @@ -11,7 +11,6 @@ import { WritableSignal, inject, signal, - ChangeDetectionStrategy, } from '@angular/core'; import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -137,7 +136,6 @@ describe('MatTabBody', () => { `, imports: [PortalModule, MatRippleModule, MatTabBody], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleTabBodyApp implements AfterViewInit { content = signal(undefined); diff --git a/src/material/tabs/tab-body.ts b/src/material/tabs/tab-body.ts index 8319c2d0a72e..0e8d459186d5 100644 --- a/src/material/tabs/tab-body.ts +++ b/src/material/tabs/tab-body.ts @@ -46,6 +46,12 @@ export class MatTabBodyPortal extends CdkPortalOutlet implements OnInit, OnDestr /** Subscription to events for when the tab body finishes leaving from center position. */ private _leavingSub = Subscription.EMPTY; + constructor(...args: unknown[]); + + constructor() { + super(); + } + /** Set initial visibility or set up subscription for changing visibility. */ override ngOnInit(): void { super.ngOnInit(); @@ -116,7 +122,7 @@ export type MatTabBodyOriginState = 'left' | 'right'; styleUrl: 'tab-body.css', encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, host: { 'class': 'mat-mdc-tab-body', // In most cases the `hidden` that we set on the off-screen content is enough @@ -186,6 +192,8 @@ export class MatTabBody implements OnInit, OnDestroy { this._computePositionAnimationState(); } + constructor(...args: unknown[]); + constructor() { if (this._dir) { const changeDetectorRef = inject(ChangeDetectorRef); diff --git a/src/material/tabs/tab-content.ts b/src/material/tabs/tab-content.ts index 7d774b5ecaad..f148342fdad0 100644 --- a/src/material/tabs/tab-content.ts +++ b/src/material/tabs/tab-content.ts @@ -22,4 +22,7 @@ export const MAT_TAB_CONTENT = new InjectionToken('MatTabContent' }) export class MatTabContent { template = inject>(TemplateRef); + + constructor(...args: unknown[]); + constructor() {} } diff --git a/src/material/tabs/tab-group.html b/src/material/tabs/tab-group.html index 99f49f0cf1cc..2db906b31bfb 100644 --- a/src/material/tabs/tab-group.html +++ b/src/material/tabs/tab-group.html @@ -65,7 +65,7 @@
    @for (tab of _tabs; track tab;) { { - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [{provide: MATERIAL_ANIMATIONS, useValue: {animationsDisabled: true}}], }); - }); + })); describe('basic behavior', () => { let fixture: ComponentFixture; @@ -65,7 +64,7 @@ describe('MatTabGroup', () => { checkSelectedIndex(2, fixture); }); - it('should support two-way binding for selectedIndex', async () => { + it('should support two-way binding for selectedIndex', fakeAsync(() => { let component = fixture.componentInstance; component.selectedIndex = 0; @@ -74,12 +73,13 @@ describe('MatTabGroup', () => { let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; tabLabel.nativeElement.click(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(component.selectedIndex).toBe(1); - }); + })); - it('should set to correct tab on fast change', () => { + // Note: needs to be `async` in order to fail when we expect it to. + it('should set to correct tab on fast change', waitForAsync(() => { let component = fixture.componentInstance; component.selectedIndex = 0; fixture.changeDetectorRef.markForCheck(); @@ -93,9 +93,9 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(component.selectedIndex).toBe(0); - }); + })); - it('should change tabs based on selectedIndex', async () => { + it('should change tabs based on selectedIndex', fakeAsync(() => { let component = fixture.componentInstance; let tabComponent = fixture.debugElement.query(By.css('mat-tab-group')).componentInstance; @@ -107,11 +107,11 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); checkSelectedIndex(2, fixture); - await fixture.whenStable(); + tick(); expect(component.handleSelection).toHaveBeenCalledTimes(1); expect(component.selectEvent.index).toBe(2); - }); + })); it('should update tab positions when selected index is changed', () => { fixture.detectChanges(); @@ -206,9 +206,9 @@ describe('MatTabGroup', () => { .toBe(0); }); - it('should set the isActive flag on each of the tabs', async () => { + it('should set the isActive flag on each of the tabs', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); + tick(); const tabs = fixture.componentInstance.tabs.toArray(); @@ -219,24 +219,24 @@ describe('MatTabGroup', () => { fixture.componentInstance.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(tabs[0].isActive).toBe(false); expect(tabs[1].isActive).toBe(false); expect(tabs[2].isActive).toBe(true); - }); + })); - it('should fire animation done event', async () => { + it('should fire animation done event', fakeAsync(() => { fixture.detectChanges(); spyOn(fixture.componentInstance, 'animationDone'); let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; tabLabel.nativeElement.click(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.componentInstance.animationDone).toHaveBeenCalledTimes(1); - }); + })); it('should add the proper `aria-setsize` and `aria-posinset`', () => { fixture.detectChanges(); @@ -312,7 +312,7 @@ describe('MatTabGroup', () => { ); }); - it('should emit focusChange when a tab receives focus', async () => { + it('should emit focusChange when a tab receives focus', fakeAsync(() => { spyOn(fixture.componentInstance, 'handleFocus'); fixture.detectChanges(); @@ -324,14 +324,14 @@ describe('MatTabGroup', () => { // index, we focus the second tab before testing the keyboard navigation. dispatchFakeEvent(tabLabels[2].nativeElement, 'focus'); fixture.detectChanges(); - await fixture.whenStable(); + flush(); fixture.detectChanges(); expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1); expect(fixture.componentInstance.handleFocus).toHaveBeenCalledWith( jasmine.objectContaining({index: 2}), ); - }); + })); it('should be able to programmatically focus a particular tab', () => { fixture.detectChanges(); @@ -384,9 +384,9 @@ describe('MatTabGroup', () => { expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '-1', '0']); }); - it('should be able to set the aria-label of the tablist', async () => { + it('should be able to set the aria-label of the tablist', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); + tick(); const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement; expect(tabList.hasAttribute('aria-label')).toBe(false); @@ -400,11 +400,11 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(tabList.hasAttribute('aria-label')).toBe(false); - }); + })); - it('should be able to set the aria-labelledby of the tablist', async () => { + it('should be able to set the aria-labelledby of the tablist', fakeAsync(() => { fixture.detectChanges(); - await fixture.whenStable(); + tick(); const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement; expect(tabList.hasAttribute('aria-labelledby')).toBe(false); @@ -418,7 +418,7 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(tabList.hasAttribute('aria-labelledby')).toBe(false); - }); + })); it('should set IDs on individual tabs and use them to label the tab bodies', () => { fixture.detectChanges(); @@ -460,36 +460,16 @@ describe('MatTabGroup', () => { }); }); - describe('animation duration', () => { - it('should set the body and header animation duration when value is a string', () => { - const fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); - fixture.detectChanges(); - - const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); - expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms'); - expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); - }); - - it('should set the body and header animation duration when value is an object', () => { - const fixture = TestBed.createComponent(TabsWithObjectAnimationDuration); - fixture.detectChanges(); - - const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); - expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('100ms'); - expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('200ms'); - }); - }); - describe('aria labelling', () => { let fixture: ComponentFixture; let tab: HTMLElement; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(TabGroupWithAriaInputs); fixture.detectChanges(); - await fixture.whenStable(); + tick(); tab = fixture.nativeElement.querySelector('.mat-mdc-tab'); - }); + })); it('should not set aria-label or aria-labelledby attributes if they are not passed in', () => { expect(tab.hasAttribute('aria-label')).toBe(false); @@ -532,12 +512,12 @@ describe('MatTabGroup', () => { let fixture: ComponentFixture; let tabPanels: HTMLElement[]; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(BindedTabsTestApp); fixture.detectChanges(); - await fixture.whenStable(); + tick(); tabPanels = Array.from(fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body')); - }); + })); it('should set `aria-hidden="true"` on inactive tab panels', () => { fixture.detectChanges(); @@ -586,14 +566,14 @@ describe('MatTabGroup', () => { describe('dynamic binding tabs', () => { let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(SimpleDynamicTabsTestApp); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); - }); + })); - it('should update selected index if the last tab removed while selected', async () => { + it('should update selected index if the last tab removed while selected', fakeAsync(() => { const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), ).componentInstance; @@ -602,16 +582,16 @@ describe('MatTabGroup', () => { fixture.componentInstance.selectedIndex = numberOfTabs - 1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); // Remove last tab while last tab is selected, expect next tab over to be selected fixture.componentInstance.tabs.pop(); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(component.selectedIndex).toBe(numberOfTabs - 2); - }); + })); it('should maintain the selected tab if a new tab is added', () => { fixture.detectChanges(); @@ -653,7 +633,7 @@ describe('MatTabGroup', () => { expect(component._tabs.toArray()[0].isActive).toBe(true); }); - it('should be able to select a new tab after creation', async () => { + it('should be able to select a new tab after creation', fakeAsync(() => { fixture.detectChanges(); const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), @@ -664,13 +644,13 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(component.selectedIndex).toBe(3); expect(component._tabs.toArray()[3].isActive).toBe(true); - }); + })); - it('should not fire `selectedTabChange` when the amount of tabs changes', async () => { + it('should not fire `selectedTabChange` when the amount of tabs changes', fakeAsync(() => { fixture.detectChanges(); fixture.componentInstance.selectedIndex = 1; fixture.changeDetectorRef.markForCheck(); @@ -681,13 +661,13 @@ describe('MatTabGroup', () => { fixture.componentInstance.tabs.unshift({label: 'New tab', content: 'at the start'}); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(fixture.componentInstance.handleSelection).not.toHaveBeenCalled(); - }); + })); - it('should update the newly-selected tab if the previously-selected tab is replaced', async () => { + it('should update the newly-selected tab if the previously-selected tab is replaced', fakeAsync(() => { const component: MatTabGroup = fixture.debugElement.query( By.css('mat-tab-group'), )!.componentInstance; @@ -700,19 +680,19 @@ describe('MatTabGroup', () => { }; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(component._tabs.get(1)?.isActive).toBe(true); expect(fixture.componentInstance.handleSelection).toHaveBeenCalledWith( jasmine.objectContaining({index: 1}), ); - }); + })); - it('should be able to disable the pagination', async () => { + it('should be able to disable the pagination', fakeAsync(() => { fixture.componentInstance.disablePagination = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); for (let i = 0; i < 50; i++) { fixture.componentInstance.tabs.push({label: `Extra ${i}`, content: ''}); @@ -720,52 +700,52 @@ describe('MatTabGroup', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect( fixture.nativeElement.querySelector('.mat-mdc-tab-header-pagination-controls-enabled'), ).toBeFalsy(); - }); + })); }); describe('async tabs', () => { let fixture: ComponentFixture; - it('should show tabs when they are available', async () => { + it('should show tabs when they are available', fakeAsync(() => { fixture = TestBed.createComponent(AsyncTabsTestApp); expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(0); fixture.detectChanges(); - await new Promise(resolve => setTimeout(resolve)); + tick(); fixture.detectChanges(); - await new Promise(resolve => setTimeout(resolve)); + tick(); expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(2); - }); + })); }); describe('with simple api', () => { let fixture: ComponentFixture; let tabGroup: MatTabGroup; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(TabGroupWithSimpleApi); fixture.detectChanges(); - await fixture.whenStable(); + tick(); tabGroup = fixture.debugElement.query(By.directive(MatTabGroup)) .componentInstance as MatTabGroup; - }); + })); - it('should support a tab-group with the simple api', async () => { + it('should support a tab-group with the simple api', fakeAsync(() => { expect(getSelectedLabel(fixture).textContent).toMatch('Junk food'); expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries'); tabGroup.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(getSelectedLabel(fixture).textContent).toMatch('Fruit'); expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes'); @@ -777,24 +757,24 @@ describe('MatTabGroup', () => { expect(getSelectedLabel(fixture).textContent).toMatch('Chips'); expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar'); - }); + })); it('should support @ViewChild in the tab content', () => { expect(fixture.componentInstance.legumes).toBeTruthy(); }); - it('should only have the active tab in the DOM', async () => { + it('should only have the active tab in the DOM', fakeAsync(() => { expect(fixture.nativeElement.textContent).toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).not.toContain('Peanuts'); tabGroup.selectedIndex = 3; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.nativeElement.textContent).not.toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).toContain('Peanuts'); - }); + })); it('should support setting the header position', () => { let tabGroupNode = fixture.debugElement.query(By.css('mat-tab-group')).nativeElement; @@ -808,7 +788,7 @@ describe('MatTabGroup', () => { expect(tabGroupNode.classList).toContain('mat-mdc-tab-group-inverted-header'); }); - it('should be able to opt into keeping the inactive tab content in the DOM', async () => { + it('should be able to opt into keeping the inactive tab content in the DOM', fakeAsync(() => { fixture.componentInstance.preserveContent = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); @@ -819,13 +799,13 @@ describe('MatTabGroup', () => { tabGroup.selectedIndex = 3; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(fixture.nativeElement.textContent).toContain('Pizza, fries'); expect(fixture.nativeElement.textContent).toContain('Peanuts'); - }); + })); - it('should visibly hide the content of inactive tabs', async () => { + it('should visibly hide the content of inactive tabs', fakeAsync(() => { const contentElements: HTMLElement[] = Array.from( fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body-content'), ); @@ -837,53 +817,51 @@ describe('MatTabGroup', () => { tabGroup.selectedIndex = 2; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(getVisibilities()).toEqual(['hidden', 'hidden', 'visible', 'hidden']); tabGroup.selectedIndex = 1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(getVisibilities()).toEqual(['hidden', 'visible', 'hidden', 'hidden']); - }); + })); }); describe('lazy loaded tabs', () => { - it('should lazy load the second tab', async () => { + it('should lazy load the second tab', fakeAsync(() => { const fixture = TestBed.createComponent(TemplateTabs); fixture.detectChanges(); - await fixture.whenStable(); + tick(); const secondLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1]; secondLabel.nativeElement.click(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); const child = fixture.debugElement.query(By.css('.child')); expect(child.nativeElement).toBeDefined(); - }); + })); }); describe('special cases', () => { - it('should not throw an error when binding isActive to the view', async () => { + it('should not throw an error when binding isActive to the view', fakeAsync(() => { const fixture = TestBed.createComponent(TabGroupWithIsActiveBinding); - await expectAsync( - (async () => { - fixture.detectChanges(); - await fixture.whenStable(); - fixture.detectChanges(); - })(), - ).not.toBeRejected(); + expect(() => { + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + }).not.toThrow(); expect(fixture.nativeElement.textContent).toContain('pizza is active'); - }); + })); - it('should not pick up mat-tab-label from a child tab', async () => { + it('should not pick up mat-tab-label from a child tab', fakeAsync(() => { const fixture = TestBed.createComponent(NestedTabGroupWithLabel); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); const labels = fixture.nativeElement.querySelectorAll('.mdc-tab__text-label'); @@ -897,14 +875,14 @@ describe('MatTabGroup', () => { 'Child 2', 'Child 3', ]); - }); + })); }); describe('nested tabs', () => { - it('should not pick up the tabs from descendant tab groups', async () => { + it('should not pick up the tabs from descendant tab groups', fakeAsync(() => { const fixture = TestBed.createComponent(NestedTabs); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); const groups = fixture.componentInstance.groups.toArray(); @@ -915,17 +893,17 @@ describe('MatTabGroup', () => { 'Inner tab one', 'Inner tab two', ]); - }); + })); - it('should pick up indirect descendant tabs', async () => { + it('should pick up indirect descendant tabs', fakeAsync(() => { const fixture = TestBed.createComponent(TabGroupWithIndirectDescendantTabs); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); const tabs = fixture.componentInstance.tabGroup._tabs; expect(tabs.map((tab: MatTab) => tab.textLabel)).toEqual(['One', 'Two']); - }); + })); }); describe('tall tabs', () => { @@ -933,10 +911,10 @@ describe('MatTabGroup', () => { window.scrollTo({top: 0}); }); - it('should not scroll when changing tabs by clicking', async () => { + it('should not scroll when changing tabs by clicking', fakeAsync(() => { const fixture = TestBed.createComponent(TabGroupWithSpaceAbove); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); window.scrollBy(0, 250); @@ -948,13 +926,13 @@ describe('MatTabGroup', () => { checkSelectedIndex(1, fixture); expect(window.scrollY).toBe(250); - await fixture.whenStable(); - }); + tick(); + })); - it('should not scroll when changing tabs programatically', async () => { + it('should not scroll when changing tabs programatically', fakeAsync(() => { const fixture = TestBed.createComponent(TabGroupWithSpaceAbove); fixture.detectChanges(); - await fixture.whenStable(); + tick(); fixture.detectChanges(); window.scrollBy(0, 250); @@ -965,8 +943,8 @@ describe('MatTabGroup', () => { fixture.detectChanges(); expect(window.scrollY).toBe(250); - await fixture.whenStable(); - }); + tick(); + })); }); describe('tabs with custom css classes', () => { @@ -1062,35 +1040,30 @@ describe('MatTabGroup', () => { }); describe('nested MatTabGroup with enabled animations', () => { - it('should not throw when creating a component with nested tab groups', async () => { - await expectAsync( - (async () => { - let fixture = TestBed.createComponent(NestedTabs); - fixture.detectChanges(); - await fixture.whenStable(); - })(), - ).not.toBeRejected(); - }); + it('should not throw when creating a component with nested tab groups', fakeAsync(() => { + expect(() => { + let fixture = TestBed.createComponent(NestedTabs); + fixture.detectChanges(); + tick(); + }).not.toThrow(); + })); - it('should not throw when setting an animationDuration without units', async () => { - await expectAsync( - (async () => { - let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); - fixture.detectChanges(); - await fixture.whenStable(); - })(), - ).not.toBeRejected(); - }); + it('should not throw when setting an animationDuration without units', fakeAsync(() => { + expect(() => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + tick(); + }).not.toThrow(); + })); - it('should set appropiate css variable given a specified animationDuration', async () => { + it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); fixture.detectChanges(); - await fixture.whenStable(); + tick(); const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); - expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms'); - expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); - }); + expect(tabGroup.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + })); }); describe('MatTabGroup with ink bar fit to content', () => { @@ -1129,10 +1102,10 @@ describe('MatTabGroup with ink bar fit to content', () => { }); }); -describe('MatTabGroup with a default config', () => { +describe('MatTabNavBar with a default config', () => { let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [ { @@ -1141,7 +1114,7 @@ describe('MatTabGroup with a default config', () => { }, ], }); - }); + })); beforeEach(() => { fixture = TestBed.createComponent(SimpleTabsTestApp); @@ -1271,7 +1244,6 @@ describe('MatTabGroup labels aligned with a config', () => { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleTabsTestApp { @ViewChild(MatTabGroup) tabGroup!: MatTabGroup; @@ -1310,7 +1282,6 @@ class SimpleTabsTestApp { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleDynamicTabsTestApp { tabs = [ @@ -1339,7 +1310,6 @@ class SimpleDynamicTabsTestApp { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BindedTabsTestApp { tabs = [ @@ -1375,7 +1345,6 @@ class BindedTabsTestApp { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DisabledTabsTestApp { @ViewChildren(MatTab) tabs!: QueryList; @@ -1394,7 +1363,6 @@ class DisabledTabsTestApp { `, imports: [MatTabsModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class AsyncTabsTestApp implements OnInit { private _tabs = [ @@ -1422,7 +1390,6 @@ class AsyncTabsTestApp implements OnInit { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithSimpleApi { preserveContent = false; @@ -1445,7 +1412,6 @@ class TabGroupWithSimpleApi { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedTabs { @ViewChildren(MatTabGroup) groups!: QueryList; @@ -1465,7 +1431,6 @@ class NestedTabs { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TemplateTabs {} @@ -1476,7 +1441,6 @@ class TemplateTabs {} `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithAriaInputs { ariaLabel!: string; @@ -1495,7 +1459,6 @@ class TabGroupWithAriaInputs { } `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithIsActiveBinding {} @@ -1507,22 +1470,9 @@ class TabGroupWithIsActiveBinding {} `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabsWithCustomAnimationDuration {} -@Component({ - template: ` - - Tab one content - Tab two content - - `, - imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, -}) -class TabsWithObjectAnimationDuration {} - @Component({ template: ` @@ -1533,7 +1483,6 @@ class TabsWithObjectAnimationDuration {} `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithIndirectDescendantTabs { @ViewChild(MatTabGroup) tabGroup!: MatTabGroup; @@ -1547,7 +1496,6 @@ class TabGroupWithIndirectDescendantTabs { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithInkBarFitToContent { fitInkBarToContent = true; @@ -1570,7 +1518,6 @@ class TabGroupWithInkBarFitToContent { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupWithSpaceAbove { @ViewChild(MatTabGroup) tabGroup!: MatTabGroup; @@ -1594,7 +1541,6 @@ class TabGroupWithSpaceAbove { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedTabGroupWithLabel {} @@ -1611,7 +1557,6 @@ class NestedTabGroupWithLabel {} `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabsWithClassesTestApp { labelClassList!: string | string[]; @@ -1630,7 +1575,6 @@ class TabsWithClassesTestApp { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabsWithAlignConfig {} @@ -1646,6 +1590,5 @@ class TabsWithAlignConfig {} `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabsWithAlignCenter {} diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts index e5cc623effc7..f55c04888402 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -39,7 +39,6 @@ import {MatTabBody} from './tab-body'; import {CdkPortalOutlet} from '@angular/cdk/portal'; import {MatTabLabelWrapper} from './tab-label-wrapper'; import {Platform} from '@angular/cdk/platform'; -import {normalizeDuration} from './paginated-tab-header'; /** @docs-private */ export interface MatTabGroupBaseHeader { @@ -51,12 +50,6 @@ export interface MatTabGroupBaseHeader { /** Possible positions for the tab header. */ export type MatTabHeaderPosition = 'above' | 'below'; -/** Possible values for the animation duration of a tab group. */ -export type MatTabGroupAnimationDuration = - | string - | number - | {body: string | number; header: string | number}; - /** Boolean constant that determines whether the tab group supports the `backgroundColor` input */ const ENABLE_BACKGROUND_INPUT = true; @@ -72,7 +65,7 @@ const ENABLE_BACKGROUND_INPUT = true; styleUrl: 'tab-group.css', encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, providers: [ { provide: MAT_TAB_GROUP, @@ -86,8 +79,7 @@ const ENABLE_BACKGROUND_INPUT = true; '[class.mat-mdc-tab-group-inverted-header]': 'headerPosition === "below"', '[class.mat-mdc-tab-group-stretch-tabs]': 'stretchTabs', '[attr.mat-align-tabs]': 'alignTabs', - '[style.--mat-tab-body-animation-duration]': '_bodyAnimationDuration', - '[style.--mat-tab-header-animation-duration]': '_headerAnimationDuration', + '[style.--mat-tab-animation-duration]': 'animationDuration', }, imports: [ MatTabHeader, @@ -108,8 +100,6 @@ export class MatTabGroup private _tabLabelSubscription = Subscription.EMPTY; private _tabBodySubscription = Subscription.EMPTY; private _diAnimationsDisabled = _animationsDisabled(); - protected _bodyAnimationDuration!: string; - protected _headerAnimationDuration!: string; /** * All tabs inside the tab group. This includes tabs that belong to groups that are nested @@ -180,20 +170,14 @@ export class MatTabGroup /** Duration for the tab animation. Will be normalized to milliseconds if no units are set. */ @Input() - get animationDuration(): MatTabGroupAnimationDuration { + get animationDuration(): string { return this._animationDuration; } - set animationDuration(value: MatTabGroupAnimationDuration) { - this._animationDuration = value; - - if (value && typeof value === 'object') { - this._bodyAnimationDuration = normalizeDuration(value.body); - this._headerAnimationDuration = normalizeDuration(value.header); - } else { - this._headerAnimationDuration = this._bodyAnimationDuration = normalizeDuration(value); - } + set animationDuration(value: string | number) { + const stringValue = value + ''; + this._animationDuration = /^\d+$/.test(stringValue) ? value + 'ms' : stringValue; } - private _animationDuration!: MatTabGroupAnimationDuration; + private _animationDuration!: string; /** * `tabindex` to be set on the inner element that wraps the tab content. Can be used for improved @@ -289,6 +273,8 @@ export class MatTabGroup /** Whether the tab group is rendered on the server. */ protected _isServer: boolean = !inject(Platform).isBrowser; + constructor(...args: unknown[]); + constructor() { const defaultConfig = inject(MAT_TABS_CONFIG, {optional: true}); @@ -591,11 +577,11 @@ export class MatTabGroup } } - protected _bodyAnimationsDisabled(): boolean { + protected _animationsDisabled(): boolean { return ( this._diAnimationsDisabled || - this._bodyAnimationDuration === '0' || - this._bodyAnimationDuration === '0ms' + this.animationDuration === '0' || + this.animationDuration === '0ms' ); } } diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index 71483fcc0b8b..296ca6ac292e 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -9,14 +9,17 @@ import { dispatchFakeEvent, dispatchKeyboardEvent, } from '@angular/cdk/testing/private'; +import {ChangeDetectorRef, Component, ViewChild, inject} from '@angular/core'; import { - ChangeDetectorRef, - Component, - ViewChild, - inject, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; + ComponentFixture, + TestBed, + discardPeriodicTasks, + fakeAsync, + flush, + flushMicrotasks, + tick, + waitForAsync, +} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MatTabHeader} from './tab-header'; @@ -27,14 +30,10 @@ describe('MatTabHeader', () => { let appComponent: SimpleTabHeaderApp; let resizeEvents: Subject; - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - beforeEach(() => { + beforeEach(waitForAsync(() => { resizeEvents = new Subject(); spyOn(TestBed.inject(SharedResizeObserver), 'observe').and.returnValue(resizeEvents); - }); + })); describe('focusing', () => { let tabListContainer: HTMLElement; @@ -321,7 +320,7 @@ describe('MatTabHeader', () => { .toBe(0); }); - it('should update the scroll distance if a tab is removed and no tabs are selected', async () => { + it('should update the scroll distance if a tab is removed and no tabs are selected', fakeAsync(() => { appComponent.selectedIndex = 0; fixture.changeDetectorRef.markForCheck(); appComponent.addTabsForScrolling(); @@ -341,12 +340,12 @@ describe('MatTabHeader', () => { appComponent.tabs = appComponent.tabs.slice(2); fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(appComponent.tabHeader.scrollDistance).toBe( appComponent.tabHeader._getMaxScrollDistance(), ); - }); + })); }); describe('rtl', () => { @@ -397,70 +396,103 @@ describe('MatTabHeader', () => { headerElement = fixture.nativeElement.querySelector('.mat-mdc-tab-header'); }); - it('should scroll towards the end while holding down the next button using a mouse', async () => { - await assertNextButtonScrolling('mousedown', 'click'); - }); + it('should scroll towards the end while holding down the next button using a mouse', fakeAsync(() => { + assertNextButtonScrolling('mousedown', 'click'); + })); - it('should scroll towards the start while holding down the prev button using a mouse', async () => { - await assertPrevButtonScrolling('mousedown', 'click'); - }); + it('should scroll towards the start while holding down the prev button using a mouse', fakeAsync(() => { + assertPrevButtonScrolling('mousedown', 'click'); + })); - it('should scroll towards the end while holding down the next button using touch', async () => { - await assertNextButtonScrolling('touchstart', 'touchend'); - }); + it('should scroll towards the end while holding down the next button using touch', fakeAsync(() => { + assertNextButtonScrolling('touchstart', 'touchend'); + })); - it('should scroll towards the start while holding down the prev button using touch', async () => { - await assertPrevButtonScrolling('touchstart', 'touchend'); - }); + it('should scroll towards the start while holding down the prev button using touch', fakeAsync(() => { + assertPrevButtonScrolling('touchstart', 'touchend'); + })); - it('should not scroll if the sequence is interrupted quickly', async () => { + it('should not scroll if the sequence is interrupted quickly', fakeAsync(() => { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - await wait(100); + tick(100); dispatchFakeEvent(headerElement, 'mouseleave'); fixture.detectChanges(); - await wait(1000); + tick(3000); expect(header.scrollDistance) .withContext('Expected not to have scrolled after a while.') .toBe(0); - }); + })); - it('should clear the timeouts on destroy', async () => { + it('should clear the timeouts on destroy', fakeAsync(() => { dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); fixture.destroy(); - // Wait to see if anything throws or leaks - await wait(100); - }); + // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. + })); - it('should clear the timeouts when reaching the end', async () => { + it('should clear the timeouts on click', fakeAsync(() => { dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - // Wait long enough to reach the end - await wait(1500); - }); + dispatchFakeEvent(nextButton, 'click'); + fixture.detectChanges(); - it('should stop scrolling if the pointer leaves the header', async () => { - expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); + // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. + })); + + it('should clear the timeouts on touchend', fakeAsync(() => { + dispatchFakeEvent(nextButton, 'touchstart'); + fixture.detectChanges(); + dispatchFakeEvent(nextButton, 'touchend'); + fixture.detectChanges(); + + // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. + })); + + it('should clear the timeouts when reaching the end', fakeAsync(() => { dispatchFakeEvent(nextButton, 'mousedown'); fixture.detectChanges(); - await wait(300); + // Simulate a very long timeout. + tick(60000); + + // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. + })); + + it('should clear the timeouts when reaching the start', fakeAsync(() => { + header.scrollDistance = Infinity; + fixture.detectChanges(); + + dispatchFakeEvent(prevButton, 'mousedown'); + fixture.detectChanges(); + + // Simulate a very long timeout. + tick(60000); + + // No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared. + })); + + it('should stop scrolling if the pointer leaves the header', fakeAsync(() => { + expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); + + dispatchFakeEvent(nextButton, 'mousedown'); + fixture.detectChanges(); + tick(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(0); - await wait(500); + tick(1000); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') @@ -470,13 +502,12 @@ describe('MatTabHeader', () => { dispatchFakeEvent(headerElement, 'mouseleave'); fixture.detectChanges(); - - await wait(200); + tick(100); expect(header.scrollDistance).toBe(previousDistance); - }); + })); - it('should not scroll when pressing the right mouse button', async () => { + it('should not scroll when pressing the right mouse button', fakeAsync(() => { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchEvent( @@ -484,31 +515,30 @@ describe('MatTabHeader', () => { createMouseEvent('mousedown', undefined, undefined, undefined, undefined, 2), ); fixture.detectChanges(); - - await wait(1000); + tick(3000); expect(header.scrollDistance) .withContext('Expected not to have scrolled after a while.') .toBe(0); - }); + })); /** * Asserts that auto scrolling using the next button works. * @param startEventName Name of the event that is supposed to start the scrolling. * @param endEventName Name of the event that is supposed to end the scrolling. */ - async function assertNextButtonScrolling(startEventName: string, endEventName: string) { + function assertNextButtonScrolling(startEventName: string, endEventName: string) { expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0); dispatchFakeEvent(nextButton, startEventName); fixture.detectChanges(); - await wait(300); + tick(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(0); - await wait(600); + tick(1000); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') @@ -516,15 +546,14 @@ describe('MatTabHeader', () => { let previousDistance = header.scrollDistance; - // Wait for interval (100ms) - await wait(200); + tick(100); expect(header.scrollDistance) .withContext('Expected to scroll again after some more time.') .toBeGreaterThan(previousDistance); dispatchFakeEvent(nextButton, endEventName); - await wait(100); + flush(); } /** @@ -532,7 +561,7 @@ describe('MatTabHeader', () => { * @param startEventName Name of the event that is supposed to start the scrolling. * @param endEventName Name of the event that is supposed to end the scrolling. */ - async function assertPrevButtonScrolling(startEventName: string, endEventName: string) { + function assertPrevButtonScrolling(startEventName: string, endEventName: string) { header.scrollDistance = Infinity; fixture.detectChanges(); @@ -542,29 +571,28 @@ describe('MatTabHeader', () => { dispatchFakeEvent(prevButton, startEventName); fixture.detectChanges(); - await wait(300); + tick(300); expect(header.scrollDistance) .withContext('Expected not to scroll after short amount of time.') .toBe(currentScroll); - // Wait to exceed delay (300 + 500 = 800ms > 650ms) - await wait(600); + tick(1000); expect(header.scrollDistance) .withContext('Expected to scroll after some time.') .toBeLessThan(currentScroll); currentScroll = header.scrollDistance; - await wait(200); + + tick(100); expect(header.scrollDistance) .withContext('Expected to scroll again after some more time.') .toBeLessThan(currentScroll); dispatchFakeEvent(nextButton, endEventName); - // Wait for any cleanup or final events - await wait(100); + flush(); } }); @@ -602,7 +630,7 @@ describe('MatTabHeader', () => { }); }); - it('should re-align the ink bar when the direction changes', async () => { + it('should re-align the ink bar when the direction changes', fakeAsync(() => { fixture = TestBed.createComponent(SimpleTabHeaderApp); fixture.detectChanges(); @@ -614,12 +642,12 @@ describe('MatTabHeader', () => { fixture.componentInstance.dir = 'rtl'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(inkBar.alignToElement).toHaveBeenCalled(); - }); + })); - it('should re-align the ink bar when the header is resized', async () => { + it('should re-align the ink bar when the header is resized', fakeAsync(() => { fixture = TestBed.createComponent(SimpleTabHeaderApp); fixture.detectChanges(); @@ -629,12 +657,13 @@ describe('MatTabHeader', () => { resizeEvents.next([]); fixture.detectChanges(); - await wait(100); + tick(32); expect(inkBar.alignToElement).toHaveBeenCalled(); - }); + discardPeriodicTasks(); + })); - it('should update arrows when the header is resized', async () => { + it('should update arrows when the header is resized', fakeAsync(() => { fixture = TestBed.createComponent(SimpleTabHeaderApp); const header = fixture.componentInstance.tabHeader; @@ -643,10 +672,11 @@ describe('MatTabHeader', () => { resizeEvents.next([]); fixture.detectChanges(); - await fixture.whenStable(); + flushMicrotasks(); expect(header._checkPaginationEnabled).toHaveBeenCalled(); - }); + discardPeriodicTasks(); + })); it('should update the pagination state if the content of the labels changes', () => { const mutationCallbacks: Function[] = []; @@ -709,7 +739,6 @@ interface Tab { } `, imports: [Dir, MatTabHeader, MatTabLabelWrapper], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleTabHeaderApp { disableRipple: boolean = false; diff --git a/src/material/tabs/tab-header.ts b/src/material/tabs/tab-header.ts index 03552a5b0810..a46c9bb1ca56 100644 --- a/src/material/tabs/tab-header.ts +++ b/src/material/tabs/tab-header.ts @@ -40,7 +40,7 @@ import {MatRipple} from '../core'; styleUrl: 'tab-header.css', encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, host: { 'class': 'mat-mdc-tab-header', '[class.mat-mdc-tab-header-pagination-controls-enabled]': '_showPaginationControls', diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts index 62252ff08fa7..5eba684bff93 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts @@ -7,16 +7,8 @@ import { dispatchMouseEvent, provideFakeDirectionality, } from '@angular/cdk/testing/private'; -import { - Component, - QueryList, - signal, - ViewChild, - ViewChildren, - WritableSignal, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, QueryList, signal, ViewChild, ViewChildren, WritableSignal} from '@angular/core'; +import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '../../core'; @@ -29,11 +21,7 @@ describe('MatTabNavBar', () => { let globalRippleOptions: RippleGlobalOptions; let resizeEvents: Subject; - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - beforeEach(() => { + beforeEach(waitForAsync(() => { globalRippleOptions = {}; dir = signal('ltr'); @@ -46,7 +34,7 @@ describe('MatTabNavBar', () => { resizeEvents = new Subject(); spyOn(TestBed.inject(SharedResizeObserver), 'observe').and.returnValue(resizeEvents); - }); + })); describe('basic behavior', () => { let fixture: ComponentFixture; @@ -131,19 +119,19 @@ describe('MatTabNavBar', () => { expect(enterEvent.defaultPrevented).toBe(true); }); - it('should re-align the ink bar when the direction changes', async () => { + it('should re-align the ink bar when the direction changes', fakeAsync(() => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); dir.set('rtl'); - await fixture.whenStable(); + tick(); fixture.detectChanges(); expect(inkBar.alignToElement).toHaveBeenCalled(); - }); + })); - it('should re-align the ink bar when the tabs list change', async () => { + it('should re-align the ink bar when the tabs list change', fakeAsync(() => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); @@ -151,10 +139,10 @@ describe('MatTabNavBar', () => { fixture.componentInstance.tabs = [1, 2, 3, 4]; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await fixture.whenStable(); + tick(); expect(inkBar.alignToElement).toHaveBeenCalled(); - }); + })); it('should re-align the ink bar when the tab labels change the width', done => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; @@ -171,17 +159,17 @@ describe('MatTabNavBar', () => { expect(spy.calls.any()).toBe(false); }); - it('should re-align the ink bar when the nav bar is resized', async () => { + it('should re-align the ink bar when the nav bar is resized', fakeAsync(() => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; spyOn(inkBar, 'alignToElement'); resizeEvents.next([]); fixture.detectChanges(); - await wait(50); + tick(32); expect(inkBar.alignToElement).toHaveBeenCalled(); - }); + })); it('should hide the ink bar when all the links are inactive', () => { const inkBar = fixture.componentInstance.tabNavBar._inkBar; @@ -204,14 +192,14 @@ describe('MatTabNavBar', () => { }); }); - it('should hide the ink bar if no tabs are active on init', async () => { + it('should hide the ink bar if no tabs are active on init', fakeAsync(() => { const fixture = TestBed.createComponent(TabBarWithInactiveTabsOnInit); fixture.detectChanges(); - await wait(50); + tick(20); // Angular turns rAF calls into 16.6ms timeouts in tests. fixture.detectChanges(); expect(fixture.nativeElement.querySelectorAll('.mdc-tab-indicator--active').length).toBe(0); - }); + })); it('should clean up the ripple event handlers on destroy', () => { let fixture: ComponentFixture = TestBed.createComponent(TabLinkWithNgIf); @@ -354,27 +342,27 @@ describe('MatTabNavBar', () => { expect(tabLinks[1].classList.contains('mdc-tab--active')).toBe(true); }); - it('should re-show the ink bar if the same tab is cleared and re-activated', async () => { + it('should re-show the ink bar if the same tab is cleared and re-activated', fakeAsync(() => { const getInkBars = () => fixture.nativeElement.querySelectorAll('.mdc-tab-indicator--active').length; const fixture = TestBed.createComponent(SimpleTabNavBarTestApp); fixture.componentInstance.activeIndex = 0; fixture.detectChanges(); - await wait(50); + tick(20); expect(getInkBars()).toBe(1); fixture.componentInstance.activeIndex = -1; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(50); + tick(20); expect(getInkBars()).toBe(0); fixture.componentInstance.activeIndex = 0; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(50); + tick(20); expect(getInkBars()).toBe(1); - }); + })); describe('ripples', () => { let fixture: ComponentFixture; @@ -507,11 +495,11 @@ describe('MatTabNavBar', () => { describe('MatTabNavBar with a default config', () => { let fixture: ComponentFixture; - beforeEach(() => { + beforeEach(fakeAsync(() => { TestBed.configureTestingModule({ providers: [{provide: MAT_TABS_CONFIG, useValue: {fitInkBarToContent: true}}], }); - }); + })); beforeEach(() => { fixture = TestBed.createComponent(TabLinkWithNgIf); @@ -528,24 +516,22 @@ describe('MatTabNavBar with a default config', () => { }); describe('MatTabNavBar with enabled animations', () => { - it('should not throw when setting an animationDuration without units', async () => { - await expectAsync( - (async () => { - let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); - fixture.detectChanges(); - await fixture.whenStable(); - })(), - ).not.toBeRejected(); - }); + it('should not throw when setting an animationDuration without units', fakeAsync(() => { + expect(() => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + tick(); + }).not.toThrow(); + })); - it('should set appropiate css variable given a specified animationDuration', async () => { + it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); fixture.detectChanges(); - await fixture.whenStable(); + tick(); const tabNavBar = fixture.nativeElement.querySelector('.mat-mdc-tab-nav-bar'); - expect(tabNavBar.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); - }); + expect(tabNavBar.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + })); }); @Component({ @@ -566,7 +552,6 @@ describe('MatTabNavBar with enabled animations', () => { Tab panel `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleTabNavBarTestApp { @ViewChild(MatTabNav) tabNavBar!: MatTabNav; @@ -592,7 +577,6 @@ class SimpleTabNavBarTestApp { Tab panel `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabLinkWithNgIf { isDestroyed = false; @@ -608,7 +592,6 @@ class TabLinkWithNgIf { Tab panel `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabBarWithInactiveTabsOnInit { tabs = [0, 1, 2]; @@ -624,7 +607,6 @@ class TabBarWithInactiveTabsOnInit { , `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabsWithCustomAnimationDuration { links = ['First', 'Second', 'Third']; @@ -638,6 +620,5 @@ class TabsWithCustomAnimationDuration { Tab panel `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabBarWithDisabledTabOnInit {} diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts index 44e2c0b90e9e..d0e39c5a6db2 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts @@ -41,11 +41,7 @@ import {BehaviorSubject, Subject} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; import {ENTER, SPACE} from '@angular/cdk/keycodes'; import {MAT_TABS_CONFIG, MatTabsConfig} from '../tab-config'; -import { - MatPaginatedTabHeader, - MatPaginatedTabHeaderItem, - normalizeDuration, -} from '../paginated-tab-header'; +import {MatPaginatedTabHeader, MatPaginatedTabHeaderItem} from '../paginated-tab-header'; import {CdkObserveContent} from '@angular/cdk/observers'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; @@ -68,11 +64,11 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; '[class.mat-accent]': 'color === "accent"', '[class.mat-warn]': 'color === "warn"', '[class._mat-animation-noopable]': '_animationsDisabled', - '[style.--mat-tab-header-animation-duration]': 'animationDuration', + '[style.--mat-tab-animation-duration]': 'animationDuration', }, encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, imports: [MatRipple, CdkObserveContent], }) export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit, AfterViewInit { @@ -93,8 +89,17 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit @Input({alias: 'mat-stretch-tabs', transform: booleanAttribute}) stretchTabs: boolean = true; - /** Duration for the header animation. Will be normalized to milliseconds if no units are set. */ - @Input({transform: normalizeDuration}) animationDuration: string = ''; + @Input() + get animationDuration(): string { + return this._animationDuration; + } + + set animationDuration(value: string | number) { + const stringValue = value + ''; + this._animationDuration = /^\d+$/.test(stringValue) ? value + 'ms' : stringValue; + } + + private _animationDuration!: string; /** Query list of all tab links of the tab navigation. */ @ContentChildren(forwardRef(() => MatTabLink), {descendants: true}) @@ -158,6 +163,8 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit @ViewChild('previousPaginator') _previousPaginator!: ElementRef; _inkBar!: MatInkBar; + constructor(...args: unknown[]); + constructor() { const defaultConfig = inject(MAT_TABS_CONFIG, {optional: true}); @@ -241,6 +248,7 @@ export class MatTabNav extends MatPaginatedTabHeader implements AfterContentInit @Component({ selector: '[mat-tab-link], [matTabLink]', exportAs: 'matTabLink', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, templateUrl: 'tab-link.html', styleUrl: 'tab-link.css', @@ -333,6 +341,8 @@ export class MatTabLink /** Unique id for the tab. */ @Input() id: string = inject(_IdGenerator).getId('mat-tab-link-'); + constructor(...args: unknown[]); + constructor() { super(); @@ -430,6 +440,7 @@ export class MatTabLink 'role': 'tabpanel', }, encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatTabNavPanel { /** Unique id for the tab panel. */ diff --git a/src/material/tabs/tab.ts b/src/material/tabs/tab.ts index 2d59fd953c12..c270ae530adb 100644 --- a/src/material/tabs/tab.ts +++ b/src/material/tabs/tab.ts @@ -43,7 +43,7 @@ export const MAT_TAB_GROUP = new InjectionToken('MAT_TAB_GROUP'); // that creating the extra class will generate more code than just duplicating the template. templateUrl: 'tab.html', // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.None, exportAs: 'matTab', providers: [{provide: MAT_TAB, useExisting: MatTab}], @@ -137,11 +137,12 @@ export class MatTab implements OnInit, OnChanges, OnDestroy { */ isActive = false; + constructor(...args: unknown[]); constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); } - ngOnChanges(changes: SimpleChanges): void { + ngOnChanges(changes: SimpleChanges): void { if (changes.hasOwnProperty('textLabel') || changes.hasOwnProperty('disabled')) { this._stateChanges.next(); } diff --git a/src/material/tabs/testing/tab-group-harness.spec.ts b/src/material/tabs/testing/tab-group-harness.spec.ts index b0ab892a9608..64f10a4dc0a3 100644 --- a/src/material/tabs/testing/tab-group-harness.spec.ts +++ b/src/material/tabs/testing/tab-group-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ComponentHarness, HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -170,7 +170,6 @@ describe('MatTabGroupHarness', () => { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabGroupHarnessTest { isDisabled = signal(false); diff --git a/src/material/tabs/testing/tab-nav-bar-harness.spec.ts b/src/material/tabs/testing/tab-nav-bar-harness.spec.ts index 1cea2b189f00..d6a48ad42e25 100644 --- a/src/material/tabs/testing/tab-nav-bar-harness.spec.ts +++ b/src/material/tabs/testing/tab-nav-bar-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -107,7 +107,6 @@ describe('MatTabNavBarHarness', () => { `, imports: [MatTabsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TabNavBarHarnessTest { activeLink = 0; diff --git a/src/material/timepicker/testing/timepicker-harness.spec.ts b/src/material/timepicker/testing/timepicker-harness.spec.ts index d732973de5b6..1cb42db51707 100644 --- a/src/material/timepicker/testing/timepicker-harness.spec.ts +++ b/src/material/timepicker/testing/timepicker-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../../core'; @@ -75,7 +75,6 @@ describe('MatTimepickerHarness', () => { `, imports: [MatTimepickerInput, MatTimepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerHarnessTest { interval = signal('4h'); diff --git a/src/material/timepicker/testing/timepicker-input-harness.spec.ts b/src/material/timepicker/testing/timepicker-input-harness.spec.ts index 0060b04485ca..0e1f3af58cb9 100644 --- a/src/material/timepicker/testing/timepicker-input-harness.spec.ts +++ b/src/material/timepicker/testing/timepicker-input-harness.spec.ts @@ -1,6 +1,6 @@ import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {DateAdapter, provideNativeDateAdapter} from '../../core'; import {MatTimepicker, MatTimepickerInput} from '../../timepicker'; @@ -166,7 +166,6 @@ describe('MatTimepickerInputHarness', () => { `, imports: [MatTimepickerInput, MatTimepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerInputHarnessTest { readonly value = signal(null); diff --git a/src/material/timepicker/testing/timepicker-toggle-harness.spec.ts b/src/material/timepicker/testing/timepicker-toggle-harness.spec.ts index 6335288dd7ba..7e2ad1dbf053 100644 --- a/src/material/timepicker/testing/timepicker-toggle-harness.spec.ts +++ b/src/material/timepicker/testing/timepicker-toggle-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {DateAdapter, provideNativeDateAdapter} from '../../core'; @@ -52,7 +52,6 @@ describe('MatTimepickerToggleHarness', () => { `, imports: [MatTimepickerInput, MatTimepicker, MatTimepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerHarnessTest { disabled = signal(false); diff --git a/src/material/timepicker/timepicker-toggle.ts b/src/material/timepicker/timepicker-toggle.ts index 0fcd14c9740e..bd2bbcbd1cc1 100644 --- a/src/material/timepicker/timepicker-toggle.ts +++ b/src/material/timepicker/timepicker-toggle.ts @@ -8,6 +8,7 @@ import { booleanAttribute, + ChangeDetectionStrategy, Component, computed, HostAttributeToken, @@ -35,6 +36,7 @@ import type {MatTimepicker} from './timepicker'; }, exportAs: 'matTimepickerToggle', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconButton], }) export class MatTimepickerToggle { diff --git a/src/material/timepicker/timepicker.spec.ts b/src/material/timepicker/timepicker.spec.ts index 0907f469f291..b351af6ed25a 100644 --- a/src/material/timepicker/timepicker.spec.ts +++ b/src/material/timepicker/timepicker.spec.ts @@ -1,13 +1,5 @@ -import { - Component, - Injector, - Provider, - signal, - ViewChild, - ViewEncapsulation, - ChangeDetectionStrategy, -} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, Injector, Provider, signal, ViewChild, ViewEncapsulation} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing'; import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../core'; import { clearElement, @@ -45,7 +37,7 @@ describe('MatTimepicker', () => { beforeEach(() => configureTestingModule()); describe('value selection', () => { - it('should only change the time part of the selected date', async () => { + it('should only change the time part of the selected date', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.componentInstance.value.set(new Date(2024, 0, 15, 0, 0, 0)); fixture.detectChanges(); @@ -54,7 +46,7 @@ describe('MatTimepicker', () => { getOptions()[3].click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); const value = fixture.componentInstance.input.value()!; expect(value).toBeTruthy(); @@ -64,9 +56,9 @@ describe('MatTimepicker', () => { expect(adapter.getHours(value)).toBe(1); expect(adapter.getMinutes(value)).toBe(30); expect(adapter.getSeconds(value)).toBe(0); - }); + })); - it('should accept the selected value and close the panel when clicking an option', async () => { + it('should accept the selected value and close the panel when clicking an option', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); const input = getInput(fixture); fixture.detectChanges(); @@ -79,7 +71,7 @@ describe('MatTimepicker', () => { getOptions()[1].click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); expect(input.value).toBe('12:30 AM'); @@ -91,9 +83,9 @@ describe('MatTimepicker', () => { value: jasmine.any(Date), }), ); - }); + })); - it('should support two-way binding on the `value` input', async () => { + it('should support two-way binding on the `value` input', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerTwoWayBinding); const input = getInput(fixture); fixture.detectChanges(); @@ -115,14 +107,14 @@ describe('MatTimepicker', () => { // Propagation from host down to input fixture.componentInstance.value.set(createTime(13, 37)); fixture.detectChanges(); - await fixture.whenStable(); + flush(); value = inputInstance.value()!; expect(adapter.getHours(value)).toBe(13); expect(adapter.getMinutes(value)).toBe(37); expectSameTime(fixture.componentInstance.value(), value); - }); + })); - it('should emit the `selected` event if the option being clicked was selected already', async () => { + it('should emit the `selected` event if the option being clicked was selected already', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.componentInstance.value.set(new Date(2024, 0, 15, 2, 30, 0)); fixture.detectChanges(); @@ -132,7 +124,7 @@ describe('MatTimepicker', () => { getOptions()[getActiveOptionIndex()].click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); expect(fixture.componentInstance.selectedSpy).toHaveBeenCalledTimes(1); @@ -142,7 +134,7 @@ describe('MatTimepicker', () => { value: jasmine.any(Date), }), ); - }); + })); }); describe('input behavior', () => { @@ -343,7 +335,7 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeFalsy(); }); - it('should close the timepicker when clicking outside', async () => { + it('should close the timepicker when clicking outside', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); getInput(fixture).click(); @@ -351,11 +343,11 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeTruthy(); document.body.click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); - }); + })); - it('should close the timepicker when tabbing away from the input', async () => { + it('should close the timepicker when tabbing away from the input', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); getInput(fixture).click(); @@ -363,11 +355,11 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeTruthy(); dispatchKeyboardEvent(getInput(fixture), 'keydown', TAB); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); - }); + })); - it('should close the timepicker when pressing escape', async () => { + it('should close the timepicker when pressing escape', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); getInput(fixture).click(); @@ -375,12 +367,12 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeTruthy(); const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should emit events on open/close', async () => { + it('should emit events on open/close', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); const {openedSpy, closedSpy} = fixture.componentInstance; @@ -394,10 +386,10 @@ describe('MatTimepicker', () => { document.body.click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(openedSpy).toHaveBeenCalledTimes(1); expect(closedSpy).toHaveBeenCalledTimes(1); - }); + })); it('should clean up the overlay if it is open on destroy', () => { const fixture = TestBed.createComponent(StandaloneTimepicker); @@ -409,7 +401,7 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeFalsy(); }); - it('should be able to open and close the panel programmatically', async () => { + it('should be able to open and close the panel programmatically', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); fixture.componentInstance.timepicker.open(); @@ -417,9 +409,9 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeTruthy(); fixture.componentInstance.timepicker.close(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); - }); + })); it('should focus the input when opened programmatically', () => { const fixture = TestBed.createComponent(StandaloneTimepicker); @@ -431,7 +423,7 @@ describe('MatTimepicker', () => { expect(document.activeElement).toBe(input); }); - it('should expose the current open state', async () => { + it('should expose the current open state', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); fixture.detectChanges(); const timepicker = fixture.componentInstance.timepicker; @@ -441,12 +433,12 @@ describe('MatTimepicker', () => { expect(timepicker.isOpen()).toBe(true); timepicker.close(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(timepicker.isOpen()).toBe(false); - }); + })); // Note: this will be a type checking error, but we check it just in case for JIT mode. - it('should do nothing if trying to open a timepicker without an input', async () => { + it('should do nothing if trying to open a timepicker without an input', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerWithoutInput); fixture.detectChanges(); fixture.componentInstance.timepicker.open(); @@ -456,10 +448,11 @@ describe('MatTimepicker', () => { expect(() => { fixture.componentInstance.timepicker.close(); fixture.detectChanges(); + flush(); }).not.toThrow(); - }); + })); - it('should be able to reopen the panel when closed by a scroll strategy', async () => { + it('should be able to reopen the panel when closed by a scroll strategy', fakeAsync(() => { const scrolledSubject = new Subject(); TestBed.resetTestingModule(); @@ -481,12 +474,12 @@ describe('MatTimepicker', () => { expect(getPanel()).toBeTruthy(); scrolledSubject.next(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(getPanel()).toBeFalsy(); fixture.componentInstance.timepicker.open(); fixture.detectChanges(); expect(getPanel()).toBeTruthy(); - }); + })); it('should be able to opt out of opening on click', () => { const fixture = TestBed.createComponent(StandaloneTimepicker); @@ -821,7 +814,7 @@ describe('MatTimepicker', () => { expect(event.defaultPrevented).toBe(true); }); - it('should select the active option and close when pressing enter', async () => { + it('should select the active option and close when pressing enter', fakeAsync(() => { const fixture = TestBed.createComponent(StandaloneTimepicker); const input = getInput(fixture); fixture.detectChanges(); @@ -841,7 +834,7 @@ describe('MatTimepicker', () => { const event = dispatchKeyboardEvent(input, 'keydown', ENTER); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(input.value).toBe('1:30 AM'); expectSameTime(fixture.componentInstance.input.value(), createTime(1, 30)); @@ -854,7 +847,7 @@ describe('MatTimepicker', () => { value: jasmine.any(Date), }), ); - }); + })); it('should not navigate using the left/right arrow keys', () => { const fixture = TestBed.createComponent(StandaloneTimepicker); @@ -1039,7 +1032,7 @@ describe('MatTimepicker', () => { expect(fixture.componentInstance.control.touched).toBe(true); }); - it('should mark the control as touched when the panel is closed', async () => { + it('should mark the control as touched when the panel is closed', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerWithForms); fixture.detectChanges(); expect(fixture.componentInstance.control.touched).toBe(false); @@ -1050,9 +1043,9 @@ describe('MatTimepicker', () => { document.body.click(); fixture.detectChanges(); - await fixture.whenStable(); + flush(); expect(fixture.componentInstance.control.touched).toBe(true); - }); + })); it('should not set the `required` error if there is no valid value in the input', () => { const fixture = TestBed.createComponent(TimepickerWithForms); @@ -1070,7 +1063,7 @@ describe('MatTimepicker', () => { expect(control.errors?.['required']).toBeFalsy(); }); - it('should set an error if the user enters an invalid time string', () => { + it('should set an error if the user enters an invalid time string', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerWithForms); const control = fixture.componentInstance.control; const input = getInput(fixture); @@ -1114,9 +1107,9 @@ describe('MatTimepicker', () => { fixture.detectChanges(); expect(control.errors?.['matTimepickerParse']).toBeFalsy(); expectSameTime(control.value, createTime(12, 10)); - }); + })); - it('should set an error if the user enters a time earlier than the minimum', async () => { + it('should set an error if the user enters a time earlier than the minimum', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerWithForms); const control = fixture.componentInstance.control; const input = getInput(fixture); @@ -1137,9 +1130,9 @@ describe('MatTimepicker', () => { fixture.componentInstance.min.set(createTime(11, 0)); fixture.detectChanges(); expect(control.errors?.['matTimepickerMin']).toBeFalsy(); - }); + })); - it('should set an error if the user enters a time later than the maximum', async () => { + it('should set an error if the user enters a time later than the maximum', fakeAsync(() => { const fixture = TestBed.createComponent(TimepickerWithForms); const control = fixture.componentInstance.control; const input = getInput(fixture); @@ -1160,7 +1153,7 @@ describe('MatTimepicker', () => { fixture.componentInstance.max.set(createTime(13, 0)); fixture.detectChanges(); expect(control.errors?.['matTimepickerMax']).toBeFalsy(); - }); + })); it('should mark the input as disabled when the form control is disabled', () => { const fixture = TestBed.createComponent(TimepickerWithForms); @@ -1442,7 +1435,6 @@ describe('MatTimepicker', () => { [tabIndex]="toggleTabIndex()"/> `, imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle], - changeDetection: ChangeDetectionStrategy.Eager, }) class StandaloneTimepicker { @ViewChild(MatTimepickerInput) input!: MatTimepickerInput; @@ -1483,7 +1475,6 @@ class StandaloneTimepicker { MatFormField, MatSuffix, ], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerInFormField { @ViewChild(MatTimepicker) timepicker!: MatTimepicker; @@ -1496,7 +1487,6 @@ class TimepickerInFormField { `, imports: [MatTimepicker, MatTimepickerInput], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerTwoWayBinding { @ViewChild(MatTimepickerInput) input!: MatTimepickerInput; @@ -1513,7 +1503,6 @@ class TimepickerTwoWayBinding { `, imports: [MatTimepicker, MatTimepickerInput, ReactiveFormsModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerWithForms { @ViewChild(MatTimepickerInput) input!: MatTimepickerInput; @@ -1530,14 +1519,12 @@ class TimepickerWithForms { `, imports: [MatTimepicker, MatTimepickerInput], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerWithMultipleInputs {} @Component({ template: '', imports: [MatTimepicker], - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerWithoutInput { @ViewChild(MatTimepicker) timepicker!: MatTimepicker; @@ -1550,6 +1537,5 @@ class TimepickerWithoutInput { `, imports: [MatTimepicker, MatTimepickerInput], encapsulation: ViewEncapsulation.ShadowDom, - changeDetection: ChangeDetectionStrategy.Eager, }) class TimepickerInShadowDom {} diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts index 83df2e3f90aa..3feafc30d24a 100644 --- a/src/material/timepicker/timepicker.ts +++ b/src/material/timepicker/timepicker.ts @@ -10,6 +10,7 @@ import { afterNextRender, AfterRenderRef, booleanAttribute, + ChangeDetectionStrategy, Component, computed, effect, @@ -115,6 +116,7 @@ export interface MatTimepickerConnectedInput { exportAs: 'matTimepicker', templateUrl: 'timepicker.html', styleUrl: 'timepicker.css', + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [MatOption], providers: [ diff --git a/src/material/timepicker/util.ts b/src/material/timepicker/util.ts index 1a97abbee2b6..f8dfa4c94e61 100644 --- a/src/material/timepicker/util.ts +++ b/src/material/timepicker/util.ts @@ -89,8 +89,6 @@ export function generateOptions( max: D, interval: number, ): MatTimepickerOption[] { - // Avoid generating intervals less than a second, because it can freeze up the browser. - interval = Math.max(interval, 1); const options: MatTimepickerOption[] = []; let current = adapter.compareTime(min, max) < 1 ? min : max; diff --git a/src/material/toolbar/testing/toolbar-harness.spec.ts b/src/material/toolbar/testing/toolbar-harness.spec.ts index 64b9257d1755..b22162f6580f 100644 --- a/src/material/toolbar/testing/toolbar-harness.spec.ts +++ b/src/material/toolbar/testing/toolbar-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentHarness, HarnessLoader} from '@angular/cdk/testing'; import {MatToolbarModule} from '../../toolbar'; import {MatToolbarHarness, MatToolbarSection} from './toolbar-harness'; @@ -71,7 +71,6 @@ describe('MatToolbarHarness', () => { `, imports: [MatToolbarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarHarnessTest {} diff --git a/src/material/toolbar/toolbar.md b/src/material/toolbar/toolbar.md index 0b1d596d8afb..f82ea1b2ad4c 100644 --- a/src/material/toolbar/toolbar.md +++ b/src/material/toolbar/toolbar.md @@ -36,13 +36,6 @@ easily accomplished with `display: flex`: "file":"toolbar-multirow-example.css", "region":"toolbar-position-content-style"}) --> -### Navigation bar - -A common use case for a toolbar is as a navigation bar containing links to -different sections of an application. - - - ### Accessibility By default, the toolbar assumes that it will be used in a purely decorative fashion and thus sets no roles, ARIA attributes, or keyboard shortcuts. This is equivalent to having a sequence of `
    ` diff --git a/src/material/toolbar/toolbar.spec.ts b/src/material/toolbar/toolbar.spec.ts index ddd2dbac9c4d..6ccb47abd8d4 100644 --- a/src/material/toolbar/toolbar.spec.ts +++ b/src/material/toolbar/toolbar.spec.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MatToolbarModule} from './index'; @@ -77,7 +77,6 @@ describe('MatToolbar', () => { `, imports: [MatToolbarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarSingleRow { toolbarColor = signal(''); @@ -91,7 +90,6 @@ class ToolbarSingleRow { `, imports: [MatToolbarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarMultipleRows {} @@ -105,7 +103,6 @@ class ToolbarMultipleRows {} `, imports: [MatToolbarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarMixedRowModes { showToolbarRow = signal(true); @@ -124,6 +121,5 @@ class ToolbarMixedRowModes { `, imports: [MatToolbarModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ToolbarMultipleIndirectRows {} diff --git a/src/material/toolbar/toolbar.ts b/src/material/toolbar/toolbar.ts index deae512e6f12..645c774c49f8 100644 --- a/src/material/toolbar/toolbar.ts +++ b/src/material/toolbar/toolbar.ts @@ -10,6 +10,7 @@ import {Platform} from '@angular/cdk/platform'; import { AfterViewInit, + ChangeDetectionStrategy, Component, ContentChildren, Directive, @@ -39,6 +40,7 @@ export class MatToolbarRow {} '[class.mat-toolbar-multiple-rows]': '_toolbarRows.length > 0', '[class.mat-toolbar-single-row]': '_toolbarRows.length === 0', }, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MatToolbar implements AfterViewInit { @@ -59,6 +61,9 @@ export class MatToolbar implements AfterViewInit { /** Reference to all toolbar row elements that have been projected. */ @ContentChildren(MatToolbarRow, {descendants: true}) _toolbarRows!: QueryList; + constructor(...args: unknown[]); + constructor() {} + ngAfterViewInit() { if (this._platform.isBrowser) { this._checkToolbarMixedModes(); diff --git a/src/material/tooltip/_m3-tooltip.scss b/src/material/tooltip/_m3-tooltip.scss index 7bbfa2be067b..ead229a8620c 100644 --- a/src/material/tooltip/_m3-tooltip.scss +++ b/src/material/tooltip/_m3-tooltip.scss @@ -7,11 +7,10 @@ $system: m3-utils.get-system($theme); @return ( - base: ( - tooltip-container-shape: map.get($system, corner-extra-small), - ), + base: (), color: ( tooltip-container-color: map.get($system, inverse-surface), + tooltip-container-shape: map.get($system, corner-extra-small), tooltip-supporting-text-color: map.get($system, inverse-on-surface), ), typography: ( diff --git a/src/material/tooltip/testing/tooltip-harness.spec.ts b/src/material/tooltip/testing/tooltip-harness.spec.ts index 031d34e41692..1d0aeccf3b71 100644 --- a/src/material/tooltip/testing/tooltip-harness.spec.ts +++ b/src/material/tooltip/testing/tooltip-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, provideZoneChangeDetection, ChangeDetectionStrategy} from '@angular/core'; +import {Component, provideZoneChangeDetection} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; @@ -68,7 +68,6 @@ describe('MatTooltipHarness', () => { `, imports: [MatTooltipModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipHarnessTest { message = 'Tooltip message'; diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts index 14d35e112833..c64d1445f9b6 100644 --- a/src/material/tooltip/tooltip.spec.ts +++ b/src/material/tooltip/tooltip.spec.ts @@ -23,12 +23,20 @@ import { ViewChild, WritableSignal, } from '@angular/core'; -import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; +import { + ComponentFixture, + fakeAsync, + flush, + TestBed, + tick, + waitForAsync, +} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import { MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltip, MatTooltipModule, + SCROLL_THROTTLE_MS, TooltipPosition, TooltipTouchGestures, } from './index'; @@ -41,11 +49,7 @@ describe('MatTooltip', () => { let platform: Platform; let focusMonitor: FocusMonitor; - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } - - beforeEach(() => { + beforeEach(waitForAsync(() => { dir = signal('ltr'); TestBed.configureTestingModule({ @@ -55,7 +59,7 @@ describe('MatTooltip', () => { overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); focusMonitor = TestBed.inject(FocusMonitor); platform = TestBed.inject(Platform); - }); + })); describe('basic usage', () => { let fixture: ComponentFixture; @@ -63,20 +67,20 @@ describe('MatTooltip', () => { let buttonElement: HTMLButtonElement; let tooltipDirective: MatTooltip; - beforeEach(async () => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); - await wait(0); + tick(); buttonDebugElement = fixture.debugElement.query(By.css('button'))!; buttonElement = buttonDebugElement.nativeElement; tooltipDirective = buttonDebugElement.injector.get(MatTooltip); - }); + })); - it('should show and hide the tooltip', async () => { + it('should show and hide the tooltip', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.detectChanges(); @@ -94,60 +98,62 @@ describe('MatTooltip', () => { expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); // After hide is called, a timeout delay is created that will to hide the tooltip. - const tooltipDelay = 10; + const tooltipDelay = 1000; tooltipDirective.hide(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(true); // After the tooltip delay elapses, expect that the tooltip is not visible. - await wait(tooltipDelay); + tick(tooltipDelay); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()).toBe(false); // On animation complete, should expect that the tooltip has been detached. finishCurrentTooltipAnimation(overlayContainerElement, false); assertTooltipInstance(tooltipDirective, false); - }); + flush(); + })); - it('should be able to re-open a tooltip if it was closed by detaching the overlay', async () => { + it('should be able to re-open a tooltip if it was closed by detaching the overlay', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); tooltipDirective._overlayRef!.detach(); - await wait(0); + tick(0); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()).toBe(false); assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); + tick(0); finishCurrentTooltipAnimation(overlayContainerElement, true); expect(tooltipDirective._isTooltipVisible()).toBe(true); - }); + flush(); + })); - it('should show with delay', async () => { + it('should show with delay', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); - const tooltipDelay = 10; + const tooltipDelay = 1000; tooltipDirective.show(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(false); fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain(''); - await wait(tooltipDelay); + tick(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(true); expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); - }); + })); - it('should be able to override the default show and hide delays', async () => { + it('should be able to override the default show and hide delays', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { provide: MAT_TOOLTIP_DEFAULT_OPTIONS, - useValue: {showDelay: 10, hideDelay: 20}, + useValue: {showDelay: 1337, hideDelay: 7331}, }, ], }); @@ -160,22 +166,23 @@ describe('MatTooltip', () => { tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(); expect(tooltipDirective._isTooltipVisible()).toBe(false); - await wait(10); + tick(1337); expect(tooltipDirective._isTooltipVisible()).toBe(true); tooltipDirective.hide(); fixture.detectChanges(); - await wait(0); + tick(); expect(tooltipDirective._isTooltipVisible()).toBe(true); - await wait(20); + tick(7331); expect(tooltipDirective._isTooltipVisible()).toBe(false); - }); + flush(); + })); - it('should be able to override the default position', async () => { + it('should be able to override the default position', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -193,14 +200,14 @@ describe('MatTooltip', () => { tooltipDirective.show(); newFixture.detectChanges(); - await wait(0); + tick(); expect(tooltipDirective.position).toBe('right'); expect(tooltipDirective._getOverlayPosition().main.overlayX).toBe('start'); expect(tooltipDirective._getOverlayPosition().fallback.overlayX).toBe('end'); - }); + })); - it('should be able to define a default (global) tooltip class', async () => { + it('should be able to define a default (global) tooltip class', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -215,7 +222,7 @@ describe('MatTooltip', () => { tooltipDirective = fixture.componentInstance.tooltip; tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(); const overlayRef = tooltipDirective._overlayRef!; const tooltipElement = overlayRef.overlayElement.querySelector( '.mat-mdc-tooltip', @@ -223,9 +230,9 @@ describe('MatTooltip', () => { expect(tooltipDirective.tooltipClass).toBe('my-default-tooltip-class'); expect(tooltipElement.classList).toContain('my-default-tooltip-class'); - }); + })); - it('should be able to provide tooltip class over the custom default one', async () => { + it('should be able to provide tooltip class over the custom default one', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -240,7 +247,7 @@ describe('MatTooltip', () => { tooltipDirective = fixture.componentInstance.tooltip; tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(); const overlayRef = tooltipDirective._overlayRef!; const tooltipElement = overlayRef.overlayElement.querySelector( '.mat-mdc-tooltip', @@ -249,9 +256,9 @@ describe('MatTooltip', () => { expect(tooltipDirective.tooltipClass).not.toBe('my-default-tooltip-class'); expect(tooltipElement.classList).not.toContain('my-default-tooltip-class'); expect(tooltipElement.classList).toContain('fixed-tooltip-class'); - }); + })); - it('should position on the bottom-left by default', async () => { + it('should position on the bottom-left by default', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -268,7 +275,7 @@ describe('MatTooltip', () => { dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 100, triggerRect.top + 100); wideFixture.detectChanges(); - await wait(0); + tick(); expect(tooltipDirective._isTooltipVisible()).toBe(true); expect(tooltipDirective._overlayRef!.overlayElement.offsetLeft).toBeLessThan( @@ -277,7 +284,7 @@ describe('MatTooltip', () => { expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBeGreaterThanOrEqual( triggerRect.bottom, ); - }); + })); it('should be able to override the default positionAtOrigin', async () => { // We don't bind mouse events on mobile devices. @@ -304,7 +311,7 @@ describe('MatTooltip', () => { dispatchMouseEvent(button, 'mouseenter', triggerRect.right - 100, triggerRect.top + 100); wideFixture.detectChanges(); - await wait(0); + await new Promise(resolve => setTimeout(resolve)); expect(tooltipDirective._isTooltipVisible()).toBe(true); const actualOffsetLeft = tooltipDirective._overlayRef!.overlayElement.offsetLeft; @@ -314,7 +321,7 @@ describe('MatTooltip', () => { expect(tooltipDirective._overlayRef!.overlayElement.offsetTop).toBe(triggerRect.top + 100); }); - it('should be able to disable tooltip interactivity', async () => { + it('should be able to disable tooltip interactivity', fakeAsync(() => { TestBed.resetTestingModule().configureTestingModule({ providers: [ { @@ -332,17 +339,17 @@ describe('MatTooltip', () => { tooltipDirective.show(); newFixture.detectChanges(); - await wait(0); + tick(); expect(tooltipDirective._overlayRef?.overlayElement.classList).toContain( 'mat-mdc-tooltip-panel-non-interactive', ); - }); + })); - it('should set a css class on the overlay panel element', async () => { + it('should set a css class on the overlay panel element', fakeAsync(() => { tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); const overlayRef = tooltipDirective._overlayRef; @@ -350,15 +357,15 @@ describe('MatTooltip', () => { expect(overlayRef!.overlayElement.classList) .withContext('Expected the overlay panel element to have the tooltip panel class set.') .toContain('mat-mdc-tooltip-panel'); - }); + })); - it('should not show if disabled', async () => { + it('should not show if disabled', fakeAsync(() => { // Test that disabling the tooltip will not set the tooltip visible tooltipDirective.disabled = true; fixture.changeDetectorRef.markForCheck(); tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(false); // Test to make sure setting disabled to false will show the tooltip @@ -367,36 +374,36 @@ describe('MatTooltip', () => { fixture.changeDetectorRef.markForCheck(); tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); - }); + })); - it('should hide if disabled while visible', async () => { + it('should hide if disabled while visible', fakeAsync(() => { // Display the tooltip with a timeout before hiding. - tooltipDirective.hideDelay = 10; + tooltipDirective.hideDelay = 1000; tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); // Set tooltip to be disabled and verify that the tooltip hides. tooltipDirective.disabled = true; - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(false); - }); + })); - it('should hide if the message is cleared while the tooltip is open', async () => { + it('should hide if the message is cleared while the tooltip is open', fakeAsync(() => { tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.componentInstance.message = ''; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(false); - }); + })); it('should not show if hide is called before delay finishes', () => { assertTooltipInstance(tooltipDirective, false); @@ -437,42 +444,42 @@ describe('MatTooltip', () => { assertTooltipInstance(tooltipDirective, false); }); - it('should not follow through with hide if show is called after', async () => { + it('should not follow through with hide if show is called after', fakeAsync(() => { tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) expect(tooltipDirective._isTooltipVisible()).toBe(true); // After hide called, a timeout delay is created that will to hide the tooltip. - const tooltipDelay = 10; + const tooltipDelay = 1000; tooltipDirective.hide(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(true); // Before delay time has passed, call show which should cancel intent to hide tooltip. tooltipDirective.show(); - await wait(tooltipDelay); + tick(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(true); - }); + })); - it('should be able to update the tooltip position while open', async () => { + it('should be able to update the tooltip position while open', fakeAsync(() => { tooltipDirective.position = 'below'; tooltipDirective.show(); - await wait(0); + tick(); assertTooltipInstance(tooltipDirective, true); spyOn(tooltipDirective._overlayRef!, 'updatePosition').and.callThrough(); tooltipDirective.position = 'above'; fixture.detectChanges(); - await wait(0); + tick(); assertTooltipInstance(tooltipDirective, true); expect(tooltipDirective._overlayRef!.updatePosition).toHaveBeenCalled(); - }); + })); - it('should update the tooltip position when the directionality changes', async () => { + it('should update the tooltip position when the directionality changes', fakeAsync(() => { tooltipDirective.position = 'right'; tooltipDirective.show(); - await wait(0); + tick(); assertTooltipInstance(tooltipDirective, true); const spy = spyOn(tooltipDirective as any, '_updatePosition').and.callThrough(); @@ -481,31 +488,31 @@ describe('MatTooltip', () => { assertTooltipInstance(tooltipDirective, true); expect(spy).toHaveBeenCalled(); - }); + })); - it('should not throw when updating the position for a closed tooltip', async () => { + it('should not throw when updating the position for a closed tooltip', fakeAsync(() => { tooltipDirective.position = 'left'; tooltipDirective.show(0); fixture.detectChanges(); - await wait(0); + tick(); tooltipDirective.hide(0); fixture.detectChanges(); - await wait(0); + tick(); finishCurrentTooltipAnimation(overlayContainerElement, false); expect(() => { tooltipDirective.position = 'right'; fixture.detectChanges(); + tick(); }).not.toThrow(); - await wait(0); - }); + })); - it('should be able to modify the tooltip message', async () => { + it('should be able to modify the tooltip message', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) expect(tooltipDirective._tooltipInstance!.isVisible()).toBe(true); fixture.detectChanges(); @@ -516,13 +523,13 @@ describe('MatTooltip', () => { fixture.detectChanges(); expect(overlayContainerElement.textContent).toContain(newMessage); - }); + })); - it('should allow extra classes to be set on the tooltip', async () => { + it('should allow extra classes to be set on the tooltip', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) fixture.detectChanges(); // Make sure classes aren't prematurely added @@ -549,25 +556,26 @@ describe('MatTooltip', () => { expect(tooltipElement.classList) .withContext('Expected to have the class after enabling matTooltipClass') .toContain('custom-two'); - }); + })); - it('should be removed after parent destroyed', async () => { + it('should be removed after parent destroyed', fakeAsync(() => { tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.destroy(); expect(overlayContainerElement.childNodes.length).toBe(0); expect(overlayContainerElement.textContent).toBe(''); - }); + flush(); + })); - it('should have an aria-describedby element with the tooltip message', async () => { + it('should have an aria-describedby element with the tooltip message', fakeAsync(() => { const dynamicTooltipsDemoFixture = TestBed.createComponent(DynamicTooltipsDemo); const dynamicTooltipsComponent = dynamicTooltipsDemoFixture.componentInstance; dynamicTooltipsComponent.tooltips = ['Tooltip One', 'Tooltip Two']; dynamicTooltipsDemoFixture.detectChanges(); - await wait(0); + tick(); const buttons = dynamicTooltipsDemoFixture.nativeElement.querySelectorAll('button'); const firstButtonAria = buttons[0].getAttribute('aria-describedby'); @@ -575,48 +583,48 @@ describe('MatTooltip', () => { const secondButtonAria = buttons[1].getAttribute('aria-describedby'); expect(document.querySelector(`#${secondButtonAria}`)!.textContent).toBe('Tooltip Two'); - }); + })); - it('should not add an ARIA description for elements that have the same text as a data-bound aria-label', async () => { + it('should not add an ARIA description for elements that have the same text as a data-bound aria-label', fakeAsync(() => { const ariaLabelFixture = TestBed.createComponent(DataBoundAriaLabelTooltip); ariaLabelFixture.detectChanges(); - await wait(0); + tick(); const button = ariaLabelFixture.nativeElement.querySelector('button'); expect(button.getAttribute('aria-describedby')).toBeFalsy(); - }); + })); - it('should toggle aria-describedby depending on whether the tooltip is disabled', async () => { + it('should toggle aria-describedby depending on whether the tooltip is disabled', fakeAsync(() => { expect(buttonElement.getAttribute('aria-describedby')).toBeTruthy(); fixture.componentInstance.tooltipDisabled = true; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(0); + tick(); expect(buttonElement.hasAttribute('aria-describedby')).toBe(false); fixture.componentInstance.tooltipDisabled = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await wait(0); + tick(); expect(buttonElement.getAttribute('aria-describedby')).toBeTruthy(); - }); + })); - it('should not try to dispose the tooltip when destroyed and done hiding', async () => { + it('should not try to dispose the tooltip when destroyed and done hiding', fakeAsync(() => { tooltipDirective.show(); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); const tooltipDelay = 1000; tooltipDirective.hide(); - await wait(tooltipDelay); // Change the tooltip state to hidden and trigger animation stat + tick(tooltipDelay); // Change the tooltip state to hidden and trigger animation start fixture.componentInstance.showButton = false; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - }); + })); - it('should complete the afterHidden stream when tooltip is destroyed', async () => { + it('should complete the afterHidden stream when tooltip is destroyed', fakeAsync(() => { tooltipDirective.show(); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -627,12 +635,12 @@ describe('MatTooltip', () => { .subscribe({complete: spy}); tooltipDirective.hide(0); - await wait(0); + tick(0); fixture.detectChanges(); expect(spy).toHaveBeenCalled(); subscription.unsubscribe(); - }); + })); it('should consistently position before and after overlay origin in ltr and rtl dir', () => { tooltipDirective.position = 'left'; @@ -683,11 +691,11 @@ describe('MatTooltip', () => { }).toThrowError('Tooltip position "everywhere" is invalid.'); }); - it('should pass the layout direction to the tooltip', async () => { + it('should pass the layout direction to the tooltip', fakeAsync(() => { dir.set('rtl'); tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); const tooltipWrapper = overlayContainerElement.querySelector( @@ -698,12 +706,12 @@ describe('MatTooltip', () => { expect(tooltipWrapper.getAttribute('dir')) .withContext('Expected tooltip to be in RTL mode.') .toBe('rtl'); - }); + })); - it('should keep the overlay direction in sync with the trigger direction', async () => { + it('should keep the overlay direction in sync with the trigger direction', fakeAsync(() => { dir.set('rtl'); tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -715,13 +723,13 @@ describe('MatTooltip', () => { .toBe('rtl'); tooltipDirective.hide(0); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, false); dir.set('ltr'); tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -731,19 +739,20 @@ describe('MatTooltip', () => { expect(tooltipWrapper.getAttribute('dir')) .withContext('Expected tooltip to be in LTR.') .toBe('ltr'); - }); + flush(); + })); - it('should be able to set the tooltip message as a number', () => { + it('should be able to set the tooltip message as a number', fakeAsync(() => { fixture.componentInstance.message = 100; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); expect(tooltipDirective.message).toBe('100'); - }); + })); - it('should hide when clicking away', async () => { + it('should hide when clicking away', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -751,18 +760,18 @@ describe('MatTooltip', () => { expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); document.body.click(); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, false); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()).toBe(false); expect(overlayContainerElement.textContent).toBe(''); - }); + })); - it('should hide when clicking away with an auxilliary button', async () => { + it('should hide when clicking away with an auxilliary button', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -770,121 +779,124 @@ describe('MatTooltip', () => { expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); dispatchFakeEvent(document.body, 'auxclick'); - await wait(0); + tick(0); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, false); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()).toBe(false); expect(overlayContainerElement.textContent).toBe(''); - }); + })); - it('should not hide immediately if a click fires while animating', async () => { + it('should not hide immediately if a click fires while animating', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); document.body.click(); fixture.detectChanges(); - await wait(0); + tick(500); finishCurrentTooltipAnimation(overlayContainerElement, true); expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); - }); + flush(); + })); - it('should hide when pressing escape', async () => { + it('should hide when pressing escape', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); + tick(500); expect(tooltipDirective._isTooltipVisible()).toBe(true); expect(overlayContainerElement.textContent).toContain(initialTooltipMessage); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, false); expect(tooltipDirective._isTooltipVisible()).toBe(false); expect(overlayContainerElement.textContent).toBe(''); - }); + flush(); + })); - it('should not throw when pressing ESCAPE', async () => { + it('should not throw when pressing ESCAPE', fakeAsync(() => { expect(() => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); }).not.toThrow(); // Flush due to the additional tick that is necessary for the FocusMonitor. - await wait(0); - }); + flush(); + })); - it('should preventDefault when pressing ESCAPE', async () => { + it('should preventDefault when pressing ESCAPE', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); - await wait(0); + flush(); expect(event.defaultPrevented).toBe(true); - }); + })); - it('should not preventDefault when pressing ESCAPE with a modifier', async () => { + it('should not preventDefault when pressing ESCAPE with a modifier', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); const event = createKeyboardEvent('keydown', ESCAPE, undefined, {alt: true}); dispatchEvent(document.body, event); fixture.detectChanges(); - await wait(0); + flush(); expect(event.defaultPrevented).toBe(false); - }); + })); - it('should not show the tooltip on programmatic focus', async () => { + it('should not show the tooltip on programmatic focus', fakeAsync(() => { patchElementFocus(buttonElement); assertTooltipInstance(tooltipDirective, false); focusMonitor.focusVia(buttonElement, 'program'); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-tooltip')).toBeNull(); - }); + })); - it('should not show the tooltip on mouse focus', async () => { + it('should not show the tooltip on mouse focus', fakeAsync(() => { patchElementFocus(buttonElement); assertTooltipInstance(tooltipDirective, false); focusMonitor.focusVia(buttonElement, 'mouse'); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-tooltip')).toBeNull(); - }); + })); - it('should not show the tooltip on touch focus', async () => { + it('should not show the tooltip on touch focus', fakeAsync(() => { patchElementFocus(buttonElement); assertTooltipInstance(tooltipDirective, false); focusMonitor.focusVia(buttonElement, 'touch'); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); expect(overlayContainerElement.querySelector('.mat-mdc-tooltip')).toBeNull(); - }); + })); - it('should not hide the tooltip when calling `show` twice in a row', async () => { + it('should not hide the tooltip when calling `show` twice in a row', fakeAsync(() => { tooltipDirective.show(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); @@ -894,15 +906,16 @@ describe('MatTooltip', () => { spyOn(overlayRef, 'detach').and.callThrough(); tooltipDirective.show(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); expect(overlayRef.detach).not.toHaveBeenCalled(); - }); + flush(); + })); - it('should set a class on the overlay panel that reflects the position', async () => { + it('should set a class on the overlay panel that reflects the position', fakeAsync(() => { // Move the element so that the primary position is always used. buttonElement.style.position = 'fixed'; buttonElement.style.top = buttonElement.style.left = '200px'; @@ -910,37 +923,37 @@ describe('MatTooltip', () => { fixture.componentInstance.message = 'hi'; fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - await setPositionAndShow('below'); + setPositionAndShow('below'); const classList = tooltipDirective._overlayRef!.overlayElement.classList; expect(classList).toContain('mat-mdc-tooltip-panel-below'); - await setPositionAndShow('above'); + setPositionAndShow('above'); expect(classList).not.toContain('mat-mdc-tooltip-panel-below'); expect(classList).toContain('mat-mdc-tooltip-panel-above'); - await setPositionAndShow('left'); + setPositionAndShow('left'); expect(classList).not.toContain('mat-mdc-tooltip-panel-above'); expect(classList).toContain('mat-mdc-tooltip-panel-left'); - await setPositionAndShow('right'); + setPositionAndShow('right'); expect(classList).not.toContain('mat-mdc-tooltip-panel-left'); expect(classList).toContain('mat-mdc-tooltip-panel-right'); - async function setPositionAndShow(position: TooltipPosition) { + function setPositionAndShow(position: TooltipPosition) { tooltipDirective.hide(0); fixture.detectChanges(); - await wait(0); + tick(0); tooltipDirective.position = position; tooltipDirective.show(0); fixture.detectChanges(); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); } - }); + })); - it('should account for RTL when setting the tooltip position class', async () => { + it('should account for RTL when setting the tooltip position class', fakeAsync(() => { // Move the element so that the primary position is always used. buttonElement.style.position = 'fixed'; buttonElement.style.top = buttonElement.style.left = '200px'; @@ -953,9 +966,9 @@ describe('MatTooltip', () => { fixture.changeDetectorRef.markForCheck(); tooltipDirective.show(0); fixture.detectChanges(); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); const classList = tooltipDirective._overlayRef!.overlayElement.classList; expect(classList).not.toContain('mat-mdc-tooltip-panel-after'); @@ -965,21 +978,21 @@ describe('MatTooltip', () => { tooltipDirective.hide(0); fixture.detectChanges(); - await wait(0); + tick(0); dir.set('rtl'); tooltipDirective.show(0); fixture.detectChanges(); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); expect(classList).not.toContain('mat-mdc-tooltip-panel-after'); expect(classList).not.toContain('mat-mdc-tooltip-panel-before'); expect(classList).not.toContain('mat-mdc-tooltip-panel-right'); expect(classList).toContain('mat-mdc-tooltip-panel-left'); - }); + })); - it('should clear the show timeout on destroy', async () => { + it('should clear the show timeout on destroy', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(1000); @@ -988,15 +1001,16 @@ describe('MatTooltip', () => { // Note that we aren't asserting anything, but `fakeAsync` will // throw if we have any timers by the end of the test. fixture.destroy(); - }); + flush(); + })); - it('should clear the hide timeout on destroy', async () => { + it('should clear the hide timeout on destroy', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); + tick(0); fixture.detectChanges(); - await wait(0); + tick(500); tooltipDirective.hide(1000); fixture.detectChanges(); @@ -1004,15 +1018,16 @@ describe('MatTooltip', () => { // Note that we aren't asserting anything, but `fakeAsync` will // throw if we have any timers by the end of the test. fixture.destroy(); - }); + flush(); + })); - it('should set the multiline class on tooltips with messages that overflow', async () => { + it('should set the multiline class on tooltips with messages that overflow', fakeAsync(() => { fixture.componentInstance.message = 'This is a very long message that should cause the' + 'tooltip message body to overflow onto a new line.'; tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(); // Need to detect changes again to wait for the multiline class to be applied. fixture.detectChanges(); @@ -1023,9 +1038,9 @@ describe('MatTooltip', () => { expect(tooltipElement.classList).toContain('mdc-tooltip--multiline'); expect(tooltipDirective._tooltipInstance?._isMultiline).toBeTrue(); - }); + })); - it('should hide on mouseleave on the trigger', async () => { + it('should hide on mouseleave on the trigger', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1033,16 +1048,16 @@ describe('MatTooltip', () => { dispatchMouseEvent(fixture.componentInstance.button.nativeElement, 'mouseenter'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); dispatchMouseEvent(fixture.componentInstance.button.nativeElement, 'mouseleave'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(false); - }); + })); - it('should not hide on mouseleave if the pointer goes from the trigger to the tooltip', async () => { + it('should not hide on mouseleave if the pointer goes from the trigger to the tooltip', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1050,7 +1065,7 @@ describe('MatTooltip', () => { dispatchMouseEvent(fixture.componentInstance.button.nativeElement, 'mouseenter'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); const tooltipElement = overlayContainerElement.querySelector( @@ -1061,11 +1076,11 @@ describe('MatTooltip', () => { dispatchEvent(fixture.componentInstance.button.nativeElement, event); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); - }); + })); - it('should hide on mouseleave on the tooltip', async () => { + it('should hide on mouseleave on the tooltip', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1073,7 +1088,7 @@ describe('MatTooltip', () => { dispatchMouseEvent(fixture.componentInstance.button.nativeElement, 'mouseenter'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); const tooltipElement = overlayContainerElement.querySelector( @@ -1081,11 +1096,11 @@ describe('MatTooltip', () => { ) as HTMLElement; dispatchMouseEvent(tooltipElement, 'mouseleave'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(false); - }); + })); - it('should not hide on mouseleave if the pointer goes from the tooltip to the trigger', async () => { + it('should not hide on mouseleave if the pointer goes from the tooltip to the trigger', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1093,7 +1108,7 @@ describe('MatTooltip', () => { dispatchMouseEvent(fixture.componentInstance.button.nativeElement, 'mouseenter'); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); const tooltipElement = overlayContainerElement.querySelector( @@ -1106,9 +1121,9 @@ describe('MatTooltip', () => { dispatchEvent(tooltipElement, event); fixture.detectChanges(); - await wait(0); + tick(0); expect(tooltipDirective._isTooltipVisible()).toBe(true); - }); + })); }); describe('fallback positions', () => { @@ -1170,14 +1185,15 @@ describe('MatTooltip', () => { tooltipDirective = buttonDebugElement.injector.get(MatTooltip); }); - it('should hide tooltip if clipped after changing positions', async () => { + it('should hide tooltip if clipped after changing positions', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); // Show the tooltip and tick for the show delay (default is 0) tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); + // Expect that the tooltip is displayed // Expect that the tooltip is displayed expect(tooltipDirective._isTooltipVisible()) .withContext('Expected tooltip to be initially visible') @@ -1185,19 +1201,19 @@ describe('MatTooltip', () => { // Scroll the page but tick just before the default throttle should update. fixture.componentInstance.scrollDown(); - await wait(0); + tick(SCROLL_THROTTLE_MS - 1); expect(tooltipDirective._isTooltipVisible()) .withContext('Expected tooltip to be visible when scrolling, before throttle limit') .toBe(true); // Finish ticking to the throttle's limit and check that the scroll event notified the // tooltip and it was hidden. - await wait(100); + tick(100); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()) .withContext('Expected tooltip hidden when scrolled out of view, after throttle limit') .toBe(false); - }); + })); }); describe('with OnPush', () => { @@ -1214,11 +1230,11 @@ describe('MatTooltip', () => { tooltipDirective = buttonDebugElement.injector.get(MatTooltip); }); - it('should show and hide the tooltip', async () => { + it('should show and hide the tooltip', fakeAsync(() => { assertTooltipInstance(tooltipDirective, false); tooltipDirective.show(); - await wait(0); // Tick for the show delay (default is 0) + tick(0); // Tick for the show delay (default is 0) expect(tooltipDirective._isTooltipVisible()).toBe(true); fixture.detectChanges(); @@ -1234,21 +1250,22 @@ describe('MatTooltip', () => { expect(tooltipElement.classList).toContain('mat-mdc-tooltip-show'); // After hide called, a timeout delay is created that will to hide the tooltip. - const tooltipDelay = 10; + const tooltipDelay = 1000; tooltipDirective.hide(tooltipDelay); expect(tooltipDirective._isTooltipVisible()).toBe(true); // After the tooltip delay elapses, expect that the tooltip is not visible. - await wait(tooltipDelay); + tick(tooltipDelay); fixture.detectChanges(); expect(tooltipDirective._isTooltipVisible()).toBe(false); // On animation complete, should expect that the tooltip has been detached. finishCurrentTooltipAnimation(overlayContainerElement, false); assertTooltipInstance(tooltipDirective, false); - }); + flush(); + })); - it('should have rendered the tooltip text on init', async () => { + it('should have rendered the tooltip text on init', fakeAsync(() => { // We don't bind mouse events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1256,13 +1273,13 @@ describe('MatTooltip', () => { dispatchFakeEvent(buttonElement, 'mouseenter'); fixture.detectChanges(); - await wait(0); + tick(0); const tooltipElement = overlayContainerElement.querySelector( '.mat-mdc-tooltip', ) as HTMLElement; expect(tooltipElement.textContent).toContain('initial tooltip message'); - }); + })); }); describe('touch gestures', () => { @@ -1270,7 +1287,6 @@ describe('MatTooltip', () => { platform.ANDROID = true; }); - // Note: switching this test away from `fakeAsync` causes it to fail only on CI. it('should have a delay when showing on touchstart', fakeAsync(() => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.detectChanges(); @@ -1290,7 +1306,7 @@ describe('MatTooltip', () => { flush(); })); - it('should be able to disable opening on touch', async () => { + it('should be able to disable opening on touch', fakeAsync(() => { const fixture = TestBed.createComponent(BasicTooltipDemo); fixture.componentInstance.touchGestures = 'off'; fixture.detectChanges(); @@ -1298,11 +1314,12 @@ describe('MatTooltip', () => { dispatchFakeEvent(button, 'touchstart'); fixture.detectChanges(); - await wait(10); // Just wait a bit to ensure no timer firs + tick(500); // Finish the delay. fixture.detectChanges(); + tick(500); // Finish the animation. assertTooltipInstance(fixture.componentInstance.tooltip, false); - }); + })); it('should not prevent the default action on touchstart', () => { const fixture = TestBed.createComponent(BasicTooltipDemo); @@ -1385,22 +1402,18 @@ describe('MatTooltip', () => { const fixture = TestBed.createComponent(TooltipOnTextFields); fixture.detectChanges(); - const inputStyle = fixture.componentInstance.input.nativeElement.style as unknown as Record< - string, - string - >; - const textareaStyle = fixture.componentInstance.textarea.nativeElement - .style as unknown as Record; + const inputStyle = fixture.componentInstance.input.nativeElement.style; + const textareaStyle = fixture.componentInstance.textarea.nativeElement.style; - expect(inputStyle['userSelect']).toBeFalsy(); - expect(inputStyle['webkitUserSelect']).toBeFalsy(); - expect(inputStyle['msUserSelect']).toBeFalsy(); - expect(inputStyle['MozUserSelect']).toBeFalsy(); + expect(inputStyle.userSelect).toBeFalsy(); + expect(inputStyle.webkitUserSelect).toBeFalsy(); + expect(inputStyle.msUserSelect).toBeFalsy(); + expect(inputStyle.MozUserSelect).toBeFalsy(); - expect(textareaStyle['userSelect']).toBeFalsy(); - expect(textareaStyle['webkitUserSelect']).toBeFalsy(); - expect(textareaStyle['msUserSelect']).toBeFalsy(); - expect(textareaStyle['MozUserSelect']).toBeFalsy(); + expect(textareaStyle.userSelect).toBeFalsy(); + expect(textareaStyle.webkitUserSelect).toBeFalsy(); + expect(textareaStyle.msUserSelect).toBeFalsy(); + expect(textareaStyle.MozUserSelect).toBeFalsy(); }); it('should disable text selection on inputs when gestures are set to on', () => { @@ -1408,22 +1421,18 @@ describe('MatTooltip', () => { fixture.componentInstance.touchGestures = 'on'; fixture.detectChanges(); - const inputStyle = fixture.componentInstance.input.nativeElement.style as unknown as Record< - string, - string - >; + const inputStyle = fixture.componentInstance.input.nativeElement.style; const inputUserSelect = - inputStyle['userSelect'] || - inputStyle['webkitUserSelect'] || - inputStyle['msUserSelect'] || - inputStyle['MozUserSelect']; - const textareaStyle = fixture.componentInstance.textarea.nativeElement - .style as unknown as Record; + inputStyle.userSelect || + inputStyle.webkitUserSelect || + inputStyle.msUserSelect || + inputStyle.MozUserSelect; + const textareaStyle = fixture.componentInstance.textarea.nativeElement.style; const textareaUserSelect = - textareaStyle['userSelect'] || - textareaStyle['webkitUserSelect'] || - textareaStyle['msUserSelect'] || - textareaStyle['MozUserSelect']; + textareaStyle.userSelect || + textareaStyle.webkitUserSelect || + textareaStyle.msUserSelect || + textareaStyle.MozUserSelect; expect(inputUserSelect).toBe('none'); expect(textareaUserSelect).toBe('none'); @@ -1510,7 +1519,7 @@ describe('MatTooltip', () => { flush(); })); - it('should not close if the cursor is over the trigger after a wheel event', async () => { + it('should not close if the cursor is over the trigger after a wheel event', fakeAsync(() => { // We don't bind wheel events on mobile devices. if (platform.IOS || platform.ANDROID) { return; @@ -1522,7 +1531,7 @@ describe('MatTooltip', () => { dispatchFakeEvent(button, 'mouseenter'); fixture.detectChanges(); - await wait(500); // Finish the open dela. + tick(500); // Finish the open delay. fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, true); assertTooltipInstance(fixture.componentInstance.tooltip, true); @@ -1537,12 +1546,13 @@ describe('MatTooltip', () => { dispatchEvent(button, wheelEvent); fixture.detectChanges(); - await wait(10); // Just wait a bt + tick(1500); // Finish the delay. fixture.detectChanges(); finishCurrentTooltipAnimation(overlayContainerElement, false); assertTooltipInstance(fixture.componentInstance.tooltip, true); - }); + flush(); + })); }); }); @@ -1557,7 +1567,6 @@ describe('MatTooltip', () => { [matTooltipDisabled]="tooltipDisabled">Button }`, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class BasicTooltipDemo { position: TooltipPosition = 'below'; @@ -1581,7 +1590,6 @@ class BasicTooltipDemo { }
    `, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ScrollableTooltipDemo { position: TooltipPosition = 'below'; @@ -1607,6 +1615,7 @@ class ScrollableTooltipDemo { [matTooltipPosition]="position"> Button `, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatTooltipModule, OverlayModule], }) class OnPushTooltipDemo { @@ -1621,7 +1630,6 @@ class OnPushTooltipDemo { } `, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DynamicTooltipsDemo { tooltips: string[] = []; @@ -1630,7 +1638,6 @@ class DynamicTooltipsDemo { @Component({ template: ``, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class DataBoundAriaLabelTooltip { message = 'Hello there'; @@ -1649,7 +1656,6 @@ class DataBoundAriaLabelTooltip { [matTooltipTouchGestures]="touchGestures"> `, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipOnTextFields { @ViewChild('input') input!: ElementRef; @@ -1666,7 +1672,6 @@ class TooltipOnTextFields { [matTooltipTouchGestures]="touchGestures"> `, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipOnDraggableElement { @ViewChild('button') button!: ElementRef; @@ -1676,7 +1681,6 @@ class TooltipOnDraggableElement { @Component({ template: ``, imports: [MatTooltip], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipDemoWithoutPositionBinding { message: string = initialTooltipMessage; @@ -1687,7 +1691,6 @@ class TooltipDemoWithoutPositionBinding { @Component({ template: ``, imports: [MatTooltip], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipDemoWithoutTooltipClassBinding { message = initialTooltipMessage; @@ -1700,7 +1703,6 @@ class TooltipDemoWithoutTooltipClassBinding { `, imports: [MatTooltip], - changeDetection: ChangeDetectionStrategy.Eager, }) class TooltipDemoWithTooltipClassBinding { message: string = initialTooltipMessage; @@ -1712,7 +1714,6 @@ class TooltipDemoWithTooltipClassBinding { styles: `button { width: 500px; height: 500px; }`, template: ``, imports: [MatTooltip], - changeDetection: ChangeDetectionStrategy.Eager, }) class WideTooltipDemo { message = 'Test'; diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index e6d9a41eb346..221b4199c4e8 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -15,6 +15,7 @@ import { import {ESCAPE, hasModifierKey} from '@angular/cdk/keycodes'; import { AfterViewInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, @@ -55,6 +56,15 @@ import {MediaMatcher} from '@angular/cdk/layout'; import {Observable, Subject} from 'rxjs'; import {_animationsDisabled} from '../core'; +declare global { + interface CSSStyleDeclaration { + msUserSelect: string; + MozUserSelect: string; + webkitUserDrag: string; + webkitTapHighlightColor: string; + } +} + /** Possible positions for a tooltip. */ export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after'; @@ -139,23 +149,12 @@ export interface MatTooltipDefaultOptions { tooltipClass?: string | string[]; /** - * By default the tooltip attempts to detect whether the user's device is able to hover by - * consulting the `Platform` provider that was created a long time ago and is based on - * some data points that may not be entirely accurate anymore (e.g. user agent string and - * Android/iOS-specific APIs), however changing them will break existing users. You can use this - * config property to opt into a more modern detection mechanism. - * - * The supported values include: - * - * - `false` - Default value. Detection is based on the `Platform` provider. - * - `true` - The tooltip will use the `any-hover` media query for more accurate detection. - * Note that this may break existing unit tests running in a headless browser. - * - `() => boolean` - If the automatic detection doesn't work properly in your case (e.g. the - * `any-hover` media query isn't supported) and you're able to detect more accurately, you can - * pass in a function that will be used for detection instead. It should return `true` if the - * device **has the ability to hover**, or `false` if it cannot. + * Whether the tooltip should use a media query to detect if the device is able to hover. + * Note that this may affect tests that run in a headless browser which reports that it's + * unable to hover. In such cases you may need to include an additional timeout, because + * the tooltip will fall back to treating the device as a touch screen. */ - detectHoverCapability?: boolean | (() => boolean); + detectHoverCapability?: boolean; } /** @@ -377,6 +376,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit { /** Whether ngOnDestroyed has been called. */ private _isDestroyed = false; + constructor(...args: unknown[]); + constructor() { const defaultOptions = this._defaultOptions; @@ -863,12 +864,6 @@ export class MatTooltip implements OnDestroy, AfterViewInit { } private _isTouchPlatform(): boolean { - const detectHoverCapability = this._defaultOptions?.detectHoverCapability; - - if (typeof detectHoverCapability === 'function') { - return !detectHoverCapability(); - } - if (this._platform.IOS || this._platform.ANDROID) { // If we detected iOS or Android, it's definitely supported. return true; @@ -877,7 +872,10 @@ export class MatTooltip implements OnDestroy, AfterViewInit { return false; } - return !!detectHoverCapability && this._mediaMatcher.matchMedia('(any-hover: none)').matches; + return ( + !!this._defaultOptions?.detectHoverCapability && + this._mediaMatcher.matchMedia('(any-hover: none)').matches + ); } /** Disables the native browser gestures, based on how the tooltip has been configured. */ @@ -886,26 +884,26 @@ export class MatTooltip implements OnDestroy, AfterViewInit { if (gestures !== 'off') { const element = this._elementRef.nativeElement; - const style = element.style as unknown as Record; + const style = element.style; // If gestures are set to `auto`, we don't disable text selection on inputs and // textareas, because it prevents the user from typing into them on iOS Safari. if (gestures === 'on' || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA')) { - style['userSelect'] = - style['msUserSelect'] = - style['webkitUserSelect'] = - style['MozUserSelect'] = + style.userSelect = + style.msUserSelect = + style.webkitUserSelect = + style.MozUserSelect = 'none'; } // If we have `auto` gestures and the element uses native HTML dragging, // we don't set `-webkit-user-drag` because it prevents the native behavior. if (gestures === 'on' || !element.draggable) { - style['webkitUserDrag'] = 'none'; + style.webkitUserDrag = 'none'; } - style['touchAction'] = 'none'; - style['webkitTapHighlightColor'] = 'transparent'; + style.touchAction = 'none'; + style.webkitTapHighlightColor = 'transparent'; } } @@ -960,6 +958,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { templateUrl: 'tooltip.html', styleUrl: 'tooltip.css', encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, host: { '(mouseleave)': '_handleMouseLeave($event)', 'aria-hidden': 'true', @@ -1016,6 +1015,10 @@ export class TooltipComponent implements OnDestroy { /** Name of the hide animation and the class that toggles it. */ private readonly _hideAnimation = 'mat-mdc-tooltip-hide'; + constructor(...args: unknown[]); + + constructor() {} + /** * Shows the tooltip with an animation originating from the provided origin * @param delay Amount of milliseconds to the delay showing the tooltip. diff --git a/src/material/tooltip/tooltip.zone.spec.ts b/src/material/tooltip/tooltip.zone.spec.ts index 1130cad2baa3..c9d9698a5bb9 100644 --- a/src/material/tooltip/tooltip.zone.spec.ts +++ b/src/material/tooltip/tooltip.zone.spec.ts @@ -6,9 +6,8 @@ import { NgZone, ViewChild, provideZoneChangeDetection, - ChangeDetectionStrategy, } from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, tick, waitForAsync} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {MatTooltipModule} from './tooltip-module'; import {MatTooltip, TooltipPosition} from './tooltip'; @@ -16,15 +15,11 @@ import {MatTooltip, TooltipPosition} from './tooltip'; const initialTooltipMessage = 'initial tooltip message'; describe('MatTooltip Zone.js integration', () => { - beforeEach(() => { + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ providers: [provideZoneChangeDetection(), provideFakeDirectionality('rtl')], }); - }); - - function wait(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); - } + })); describe('scrollable usage', () => { let fixture: ComponentFixture; @@ -38,24 +33,24 @@ describe('MatTooltip Zone.js integration', () => { tooltipDirective = buttonDebugElement.injector.get(MatTooltip); }); - it('should execute the `hide` call, after scrolling away, inside the NgZone', async () => { + it('should execute the `hide` call, after scrolling away, inside the NgZone', fakeAsync(() => { const inZoneSpy = jasmine.createSpy('in zone spy'); tooltipDirective.show(); fixture.detectChanges(); - await wait(0); + tick(0); spyOn(tooltipDirective._tooltipInstance!, 'hide').and.callFake(() => { inZoneSpy(NgZone.isInAngularZone()); }); fixture.componentInstance.scrollDown(); - await wait(100); + tick(100); fixture.detectChanges(); expect(inZoneSpy).toHaveBeenCalled(); expect(inZoneSpy).toHaveBeenCalledWith(true); - }); + })); }); }); @@ -71,7 +66,6 @@ describe('MatTooltip Zone.js integration', () => { }
    `, imports: [MatTooltipModule, OverlayModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class ScrollableTooltipDemo { position: TooltipPosition = 'below'; diff --git a/src/material/tree/node.ts b/src/material/tree/node.ts index f09fbe0fa71f..608d63da6943 100644 --- a/src/material/tree/node.ts +++ b/src/material/tree/node.ts @@ -105,6 +105,8 @@ export class MatTreeNode extends CdkTreeNode implements OnInit, this.isDisabled = value; } + constructor(...args: unknown[]); + constructor() { super(); diff --git a/src/material/tree/testing/tree-harness.spec.ts b/src/material/tree/testing/tree-harness.spec.ts index 467c9e83a3e0..c16ba77e3954 100644 --- a/src/material/tree/testing/tree-harness.spec.ts +++ b/src/material/tree/testing/tree-harness.spec.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {FlatTreeControl, NestedTreeControl} from '@angular/cdk/tree'; import { @@ -89,7 +89,7 @@ describe('MatTreeHarness', () => { expect(await firstGroup.isExpanded()).toBe(false); }); - it('should correctly get tree structure (flat tree)', async () => { + it('should correctly get tree structure', async () => { const trees = await loader.getAllHarnesses(MatTreeHarness); const flatTree = trees[0]; @@ -127,7 +127,7 @@ describe('MatTreeHarness', () => { }); }); - it('should correctly get tree structure (nested tree)', async () => { + it('should correctly get tree structure', async () => { const trees = await loader.getAllHarnesses(MatTreeHarness); const nestedTree = trees[1]; expect(await nestedTree.getTreeStructure()).toEqual({ @@ -247,7 +247,6 @@ interface ExampleFlatNode { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class TreeHarnessTest { private _transformer = (node: Node, level: number) => { diff --git a/src/material/tree/tree-using-legacy-key-manager.spec.ts b/src/material/tree/tree-using-legacy-key-manager.spec.ts index 4b9d956ed560..2ab2c188600c 100644 --- a/src/material/tree/tree-using-legacy-key-manager.spec.ts +++ b/src/material/tree/tree-using-legacy-key-manager.spec.ts @@ -1,11 +1,4 @@ -import { - Component, - ElementRef, - QueryList, - ViewChild, - ViewChildren, - ChangeDetectionStrategy, -} from '@angular/core'; +import {Component, ElementRef, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {of} from 'rxjs'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {MatTreeModule} from './tree-module'; @@ -101,7 +94,6 @@ class MinimalTestData { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatTreeApp { isExpandable = (node: MinimalTestData) => node.children.length > 0; diff --git a/src/material/tree/tree-using-tree-control.spec.ts b/src/material/tree/tree-using-tree-control.spec.ts index 094b42a88ff4..8c988d71f330 100644 --- a/src/material/tree/tree-using-tree-control.spec.ts +++ b/src/material/tree/tree-using-tree-control.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ import {FlatTreeControl, NestedTreeControl, TreeControl} from '@angular/cdk/tree'; -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {AsyncPipe} from '@angular/common'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {BehaviorSubject, Observable} from 'rxjs'; @@ -22,14 +22,14 @@ import { type NodeContent = string[]; type TreeContent = NodeContent[]; -describe('MatTree using TreeControl', () => { +describe('MatTree', () => { /** Represents an indent for expectNestedTreeToMatch */ const _ = ''; let treeElement: HTMLElement; let underlyingDataSource: FakeDataSource; describe('flat tree', () => { - describe('flat tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: SimpleMatTreeApp; @@ -41,14 +41,14 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should initialize flat tree with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); expect(nodes[0].classList).toContain('customNodeClass'); }); - it('should initialize flat tree with the right accessibility roles', () => { + it('with the right accessibility roles', () => { expect(treeElement.getAttribute('role')).toBe('tree'); getNodes(treeElement).forEach(node => { @@ -56,7 +56,7 @@ describe('MatTree using TreeControl', () => { }); }); - it('should initialize flat tree with the right aria-level attrs', () => { + it('with the right aria-level attrs', () => { // add a child to the first node const data = underlyingDataSource.data; underlyingDataSource.addChild(data[2]); @@ -67,7 +67,7 @@ describe('MatTree using TreeControl', () => { expect(ariaLevels).toEqual(['1', '1', '1', '2']); }); - it('should initialize flat tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { // add a child to the first node const data = underlyingDataSource.data; underlyingDataSource.addChild(data[2]); @@ -82,7 +82,7 @@ describe('MatTree using TreeControl', () => { expect(ariaExpandedStates).toEqual([null, null, 'true', null]); }); - it('should initialize flat tree with the right data', () => { + it('with the right data', () => { expect(underlyingDataSource.data.length).toBe(3); const data = underlyingDataSource.data; @@ -108,7 +108,7 @@ describe('MatTree using TreeControl', () => { }); }); - describe('flat tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: MatTreeAppWithToggle; @@ -120,7 +120,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should expand/collapse the flat tree node', () => { + it('should expand/collapse the node', () => { expect(underlyingDataSource.data.length).toBe(3); expect(component.treeControl.expansionModel.selected.length) @@ -184,7 +184,7 @@ describe('MatTree using TreeControl', () => { ); }); - it('should expand/collapse the flat tree node recursively', () => { + it('should expand/collapse the node recursively', () => { expect(underlyingDataSource.data.length).toBe(3); expect(component.treeControl.expansionModel.selected.length) @@ -249,7 +249,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should render flat tree with the right data', () => { + it('with the right data', () => { expectFlatTreeToMatch( treeElement, 28, @@ -263,7 +263,7 @@ describe('MatTree using TreeControl', () => { }); describe('flat tree with undefined or null children', () => { - describe('flat tree with undefined or null children should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; beforeEach(() => { @@ -272,7 +272,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should initialize flat tree with undefined or null children with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); @@ -282,7 +282,7 @@ describe('MatTree using TreeControl', () => { }); describe('nested tree with undefined or null children', () => { - describe('nested tree with undefined or null children should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; beforeEach(() => { @@ -291,7 +291,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should initialize nested tree with undefined or null children with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); @@ -300,7 +300,7 @@ describe('MatTree using TreeControl', () => { }); }); describe('nested tree', () => { - describe('nested tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: NestedMatTreeApp; @@ -312,14 +312,14 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should initialize nested tree with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); expect(nodes[0].classList).toContain('customNodeClass'); }); - it('should initialize nested tree with the right accessibility roles', () => { + it('with the right accessibility roles', () => { expect(treeElement.getAttribute('role')).toBe('tree'); getNodes(treeElement).forEach(node => { @@ -327,7 +327,7 @@ describe('MatTree using TreeControl', () => { }); }); - it('should initialize nested tree with the right data', () => { + it('with the right data', () => { expect(underlyingDataSource.data.length).toBe(3); let data = underlyingDataSource.data; @@ -415,7 +415,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should render nested tree with the right data', () => { + it('with the right data', () => { expectNestedTreeToMatch( treeElement, [`topping_1 - cheese_1 + base_1`], @@ -426,7 +426,7 @@ describe('MatTree using TreeControl', () => { }); }); - describe('nested tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: NestedMatTreeAppWithToggle; @@ -438,7 +438,7 @@ describe('MatTree using TreeControl', () => { fixture.detectChanges(); }); - it('should initialize nested tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { let ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); expect(ariaExpandedStates).toEqual([null, null, null]); @@ -457,7 +457,7 @@ describe('MatTree using TreeControl', () => { expect(ariaExpanded).toEqual([null, 'true', 'false', null]); }); - it('should expand/collapse the nested tree node', () => { + it('should expand/collapse the node', () => { component.toggleRecursively = false; const data = underlyingDataSource.data; const child = underlyingDataSource.addChild(data[1]); @@ -502,7 +502,7 @@ describe('MatTree using TreeControl', () => { .toBe(0); }); - it('should expand/collapse the nested tree node recursively', () => { + it('should expand/collapse the node recursively', () => { const data = underlyingDataSource.data; const child = underlyingDataSource.addChild(data[1]); underlyingDataSource.addChild(child); @@ -856,7 +856,6 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeCont `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatTreeApp { getLevel = (node: TestData) => node.level; @@ -935,7 +934,6 @@ const TREE_DATA: FoodNode[] = [ `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTreeWithNullOrUndefinedChild { private _transformer = (node: FoodNode, level: number) => { @@ -973,7 +971,6 @@ class MatTreeWithNullOrUndefinedChild { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatNestedTreeWithNullOrUndefinedChild { treeControl!: NestedTreeControl; @@ -1000,7 +997,6 @@ class MatNestedTreeWithNullOrUndefinedChild { `, imports: [MatTreeModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMatTreeApp { getChildren = (node: TestData) => node.observableChildren; @@ -1038,7 +1034,6 @@ class NestedMatTreeApp { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeNestedMatTreeApp { isSpecial = (_: number, node: TestData) => node.isSpecial; @@ -1072,7 +1067,6 @@ class WhenNodeNestedMatTreeApp { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1122,7 +1116,6 @@ class MatTreeAppWithToggle { `, imports: [MatTreeModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMatTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1160,7 +1153,6 @@ class NestedMatTreeAppWithToggle { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeMatTreeApp { isSpecial = (_: number, node: TestData) => node.isSpecial; diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index 43254420962b..63d55039e87d 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {Component, ViewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, ViewChild} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {AsyncPipe} from '@angular/common'; import {BehaviorSubject, Observable} from 'rxjs'; @@ -22,7 +22,7 @@ describe('MatTree', () => { let underlyingDataSource: FakeDataSource; describe('flat tree', () => { - describe('flat tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: SimpleMatTreeApp; @@ -34,14 +34,14 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should initialize flat tree with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); expect(nodes[0].classList).toContain('customNodeClass'); }); - it('should initialize flat tree with the right accessibility roles', () => { + it('with the right accessibility roles', () => { expect(treeElement.getAttribute('role')).toBe('tree'); getNodes(treeElement).forEach(node => { @@ -49,7 +49,7 @@ describe('MatTree', () => { }); }); - it('should initialize flat tree with the right aria-level attrs', () => { + it('with the right aria-level attrs', () => { // add a child to the first node const data = underlyingDataSource.data; underlyingDataSource.addChild(data[2]); @@ -60,7 +60,7 @@ describe('MatTree', () => { expect(ariaLevels).toEqual(['1', '1', '1', '2']); }); - it('should initialize flat tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { // add a child to the first node const data = underlyingDataSource.data; underlyingDataSource.addChild(data[2]); @@ -75,7 +75,7 @@ describe('MatTree', () => { expect(ariaExpandedStates).toEqual([null, null, 'true', null]); }); - it('should initialize flat tree with the right data', () => { + it('with the right data', () => { expect(underlyingDataSource.data.length).toBe(3); const data = underlyingDataSource.data; @@ -101,7 +101,7 @@ describe('MatTree', () => { }); }); - describe('flat tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: MatTreeAppWithToggle; @@ -113,7 +113,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should expand/collapse the flat tree node', () => { + it('should expand/collapse the node', () => { expect(underlyingDataSource.data.length).toBe(3); let numExpandedNodes = @@ -175,7 +175,7 @@ describe('MatTree', () => { ); }); - it('should expand/collapse the flat tree node recursively', () => { + it('should expand/collapse the node recursively', () => { expect(underlyingDataSource.data.length).toBe(3); let numExpandedNodes = @@ -238,7 +238,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should render flat tree with the right data', () => { + it('with the right data', () => { expectFlatTreeToMatch( treeElement, 28, @@ -252,7 +252,7 @@ describe('MatTree', () => { }); describe('flat tree with undefined or null children', () => { - describe('flat tree with undefined or null children should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; beforeEach(() => { @@ -261,7 +261,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should initialize flat tree with undefined or null children with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); @@ -271,7 +271,7 @@ describe('MatTree', () => { }); describe('nested tree with undefined or null children', () => { - describe('nested tree with undefined or null children should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; beforeEach(() => { @@ -280,7 +280,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should initialize nested tree with undefined or null children with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); @@ -289,7 +289,7 @@ describe('MatTree', () => { }); }); describe('nested tree', () => { - describe('nested tree should initialize', () => { + describe('should initialize', () => { let fixture: ComponentFixture; let component: NestedMatTreeApp; @@ -303,14 +303,14 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should initialize nested tree with rendered dataNodes', () => { + it('with rendered dataNodes', () => { const nodes = getNodes(treeElement); expect(nodes).withContext('Expect nodes to be defined').toBeDefined(); expect(nodes[0].classList).toContain('customNodeClass'); }); - it('should initialize nested tree with the right accessibility roles', () => { + it('with the right accessibility roles', () => { expect(treeElement.getAttribute('role')).toBe('tree'); getNodes(treeElement).forEach(node => { @@ -318,7 +318,7 @@ describe('MatTree', () => { }); }); - it('should initialize nested tree with the right data', () => { + it('with the right data', () => { expect(underlyingDataSource.data.length).toBe(3); let data = underlyingDataSource.data; @@ -406,7 +406,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should render nested tree with the right data', () => { + it('with the right data', () => { expectNestedTreeToMatch( treeElement, [`topping_1 - cheese_1 + base_1`], @@ -417,7 +417,7 @@ describe('MatTree', () => { }); }); - describe('nested tree with toggle', () => { + describe('with toggle', () => { let fixture: ComponentFixture; let component: NestedMatTreeAppWithToggle; @@ -429,7 +429,7 @@ describe('MatTree', () => { fixture.detectChanges(); }); - it('should initialize nested tree with the right aria-expanded attrs', () => { + it('with the right aria-expanded attrs', () => { expect( getNodes(treeElement) .map(x => `${x.getAttribute('aria-expanded')}`) @@ -456,7 +456,7 @@ describe('MatTree', () => { .toEqual('null, true, false, null'); }); - it('should expand/collapse the nested tree node', () => { + it('should expand/collapse the node', () => { component.toggleRecursively = false; const data = underlyingDataSource.data; const child = underlyingDataSource.addChild(data[1]); @@ -500,7 +500,7 @@ describe('MatTree', () => { expect(numExpandedNodes).withContext(`Expect node collapsed`).toBe(0); }); - it('should expand/collapse the nested tree node recursively', () => { + it('should expand/collapse the node recursively', () => { const data = underlyingDataSource.data; const child = underlyingDataSource.addChild(data[1]); underlyingDataSource.addChild(child); @@ -859,7 +859,6 @@ function expectNestedTreeToMatch(treeElement: Element, ...expectedTree: TreeCont `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class SimpleMatTreeApp { getLevel = (node: TestData) => node.level; @@ -910,7 +909,6 @@ const TREE_DATA: FoodNode[] = [ `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTreeWithNullOrUndefinedChild { childrenAccessor = (node: FoodNode): FoodNode[] => node.children || []; @@ -932,7 +930,6 @@ class MatTreeWithNullOrUndefinedChild { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatNestedTreeWithNullOrUndefinedChild { childrenAccessor = (node: FoodNode): FoodNode[] => node.children || []; @@ -956,7 +953,6 @@ class MatNestedTreeWithNullOrUndefinedChild { `, imports: [MatTreeModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMatTreeApp { childrenAccessor = (node: TestData) => node.observableChildren; @@ -994,7 +990,6 @@ class NestedMatTreeApp { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeNestedMatTreeApp { isSpecial = (_: number, node: TestData) => node.isSpecial; @@ -1030,7 +1025,6 @@ class WhenNodeNestedMatTreeApp { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class MatTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1060,7 +1054,6 @@ class MatTreeAppWithToggle { `, imports: [MatTreeModule, AsyncPipe], - changeDetection: ChangeDetectionStrategy.Eager, }) class NestedMatTreeAppWithToggle { toggleRecursively: boolean = true; @@ -1094,7 +1087,6 @@ class NestedMatTreeAppWithToggle { `, imports: [MatTreeModule], - changeDetection: ChangeDetectionStrategy.Eager, }) class WhenNodeMatTreeApp { isSpecial = (_: number, node: TestData) => node.isSpecial; diff --git a/src/material/tree/tree.ts b/src/material/tree/tree.ts index 80f0cd78d756..6a40eb55b859 100644 --- a/src/material/tree/tree.ts +++ b/src/material/tree/tree.ts @@ -24,7 +24,7 @@ import {MatTreeNodeOutlet} from './outlet'; encapsulation: ViewEncapsulation.None, // See note on CdkTree for explanation on why this uses the default change detection strategy. // tslint:disable-next-line:validate-decorators - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.Default, providers: [{provide: CdkTree, useExisting: MatTree}], imports: [MatTreeNodeOutlet], }) diff --git a/src/tsconfig-legacy.json b/src/tsconfig-legacy.json index 7814eae73441..b898a99551ef 100644 --- a/src/tsconfig-legacy.json +++ b/src/tsconfig-legacy.json @@ -10,7 +10,6 @@ }, "types": ["jasmine"], "emitDecoratorMetadata": true, - "noUncheckedSideEffectImports": false, "experimentalDecorators": true }, "include": ["**/*.spec.ts", "dev-mode-types.d.ts", "../node_modules/zone.js/zone.d.ts"], diff --git a/src/universal-app/hydration.e2e.spec.ts b/src/universal-app/hydration.e2e.spec.ts index 3be46a7634ed..ace95ae861dc 100644 --- a/src/universal-app/hydration.e2e.spec.ts +++ b/src/universal-app/hydration.e2e.spec.ts @@ -1,5 +1,14 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; +declare global { + interface Window { + ngDevMode: { + hydratedComponents: number; + componentsSkippedHydration: number; + }; + } +} + describe('hydration e2e', () => { beforeEach(async () => { await browser.waitForAngularEnabled(false); @@ -26,18 +35,8 @@ async function getHydrationState() { return browser.executeScript<{ hydratedComponents: number; componentsSkippedHydration: number; - }>(() => { - const devModeWindow = window as Window & - typeof globalThis & { - ngDevMode: { - hydratedComponents: number; - componentsSkippedHydration: number; - }; - }; - - return { - hydratedComponents: devModeWindow.ngDevMode.hydratedComponents, - componentsSkippedHydration: devModeWindow.ngDevMode.componentsSkippedHydration, - }; - }); + }>(() => ({ + hydratedComponents: window.ngDevMode.hydratedComponents, + componentsSkippedHydration: window.ngDevMode.componentsSkippedHydration, + })); } diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index fb481eadca92..7b97a7860eca 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -641,6 +641,14 @@

    Google Map

    + + diff --git a/src/universal-app/kitchen-sink/kitchen-sink.ts b/src/universal-app/kitchen-sink/kitchen-sink.ts index 9155ecca9111..5b63d99e7f81 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.ts +++ b/src/universal-app/kitchen-sink/kitchen-sink.ts @@ -12,6 +12,7 @@ import { MapBicyclingLayer, MapCircle, MapGroundOverlay, + MapHeatmapLayer, MapInfoWindow, MapKmlLayer, MapMarker, @@ -199,6 +200,7 @@ export class TestEntryComponent {} MapBicyclingLayer, MapCircle, MapGroundOverlay, + MapHeatmapLayer, MapInfoWindow, MapKmlLayer, MapMarker, diff --git a/src/youtube-player/BUILD.bazel b/src/youtube-player/BUILD.bazel index 641ef328868b..38061d0702e1 100644 --- a/src/youtube-player/BUILD.bazel +++ b/src/youtube-player/BUILD.bazel @@ -79,7 +79,6 @@ ng_project( ":youtube-player", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", - "//:node_modules/@types/youtube", "//:node_modules/rxjs", ], ) diff --git a/src/youtube-player/README.md b/src/youtube-player/README.md index 7f08775d06c7..348094cd2f24 100644 --- a/src/youtube-player/README.md +++ b/src/youtube-player/README.md @@ -8,7 +8,8 @@ File any bugs against the [angular/components repo](https://github.com/angular/c To install, run `ng add @angular/youtube-player`. ## Usage -Import `YouTubePlayer` into your component. Then add the ` +
    `; } - - private _escapeHtml(text: string): string { - if (!text) { - return text; - } - - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } } diff --git a/tools/tsconfig.json b/tools/tsconfig.json index c77c2883f0f0..810d8d3a6e3f 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -9,6 +9,7 @@ "esModuleInterop": true, "lib": ["es2020"], "skipLibCheck": true, + "downlevelIteration": true, "types": ["node"] }, "exclude": ["**/*.spec.ts", "**/*.spec.mts", "adev-api-extraction/**"] diff --git a/tsconfig.json b/tsconfig.json index 83da867646f1..2184a3e8fd14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ { "compilerOptions": { "outDir": "./dist/packages/all", - "rootDir": "./", "experimentalDecorators": true, "module": "esnext", "moduleResolution": "bundler", diff --git a/tslint.json b/tslint.json index 6bb013e2abde..c99361b9aadc 100644 --- a/tslint.json +++ b/tslint.json @@ -110,10 +110,11 @@ }, "excludeFiles": ["**/dev-app/**", "**/docs/**", "**/*.spec.ts"] }, + // Enforce OnPush & standalone even in the dev-app. { "argument": 0, "properties": { - "!changeDetection": ".*" + "changeDetection": "\\.OnPush$" }, "excludeFiles": ["**/docs/**", "**/*.spec.ts"] }