From 3280dfb20804694229a6e2d3a68917b16a1b0b9b Mon Sep 17 00:00:00 2001 From: Ogun Babacan Date: Thu, 14 Nov 2024 14:24:23 +0300 Subject: [PATCH 01/25] docs: implement trendyol tool hedwig to collect feedback (#957) (#959) --- .storybook/preview-head.html | 1 + 1 file changed, 1 insertion(+) diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index ecaf83c8..3b8b6b77 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1,3 +1,4 @@ + From e3838d32c326e450e6af2b851f4829f795e725e2 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 14:20:38 +0300 Subject: [PATCH 02/25] doc: add adr documents for dropdown --- src/components/dropdown/doc/ADR.md | 0 .../dropdown/docs/ADR/event-system.md | 90 +++++++++++++++++++ .../dropdown/docs/ADR/group-support.md | 64 +++++++++++++ .../dropdown/docs/ADR/icon-implementation.md | 42 +++++++++ .../dropdown/docs/ADR/keyboard-navigation.md | 46 ++++++++++ .../dropdown/docs/ADR/variant-system.md | 66 ++++++++++++++ 6 files changed, 308 insertions(+) delete mode 100644 src/components/dropdown/doc/ADR.md create mode 100644 src/components/dropdown/docs/ADR/event-system.md create mode 100644 src/components/dropdown/docs/ADR/group-support.md create mode 100644 src/components/dropdown/docs/ADR/icon-implementation.md create mode 100644 src/components/dropdown/docs/ADR/keyboard-navigation.md create mode 100644 src/components/dropdown/docs/ADR/variant-system.md diff --git a/src/components/dropdown/doc/ADR.md b/src/components/dropdown/doc/ADR.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/dropdown/docs/ADR/event-system.md b/src/components/dropdown/docs/ADR/event-system.md new file mode 100644 index 00000000..f7b592fd --- /dev/null +++ b/src/components/dropdown/docs/ADR/event-system.md @@ -0,0 +1,90 @@ +# Event System in Dropdown Component + +## Status +Implemented + +## Context +The dropdown component needs a robust event system to handle user interactions, state changes, and provide hooks for external integration. This system should be consistent, predictable, and follow web component best practices. + +## Decision +Implement a comprehensive event system with the following events: + +1. State Change Events: + - `bl-dropdown-open`: Fired when dropdown is opened + - `bl-dropdown-close`: Fired when dropdown is closed + +2. Interaction Events: + - `bl-dropdown-item-click`: Fired when an item is clicked + +All events will: +- Use the `bl-` prefix for consistency +- Include relevant detail data +- Follow CustomEvent pattern +- Be cancelable where appropriate + +## Implementation Details +```typescript +@customElement(blDropdownTag) +export default class BlDropdown extends LitElement { + @event("bl-dropdown-open") + private onOpen: EventDispatcher; + + @event("bl-dropdown-close") + private onClose: EventDispatcher; + + open() { + this._isPopoverOpen = true; + this._popover.show(); + this.onOpen("Dropdown opened!"); + } + + close() { + this._isPopoverOpen = false; + this._popover.visible && this._popover.hide(); + this.onClose("Dropdown closed!"); + } +} + +// Item Events +@customElement(blDropdownItemTag) +export default class BlDropdownItem extends LitElement { + @event("bl-dropdown-item-click") + private onClick: EventDispatcher; + + private _handleClick() { + this.BlDropdownField?.close(); + this.onClick("Action clicked!"); + } +} +``` + +## Event Usage +```typescript +// Event Listeners +dropdown.addEventListener('bl-dropdown-open', (e) => { + console.log('Dropdown opened:', e.detail); +}); + +dropdown.addEventListener('bl-dropdown-close', (e) => { + console.log('Dropdown closed:', e.detail); +}); + +dropdownItem.addEventListener('bl-dropdown-item-click', (e) => { + console.log('Item clicked:', e.detail); +}); +``` + +## Consequences + +### Positive +- Consistent event naming convention +- Clear separation of concerns +- Predictable behavior +- Easy integration with external systems +- Good developer experience + +### Negative +- Need to maintain event documentation +- Potential memory leaks if listeners aren't properly removed +- Additional complexity in event handling +- Need to ensure event bubbling works correctly diff --git a/src/components/dropdown/docs/ADR/group-support.md b/src/components/dropdown/docs/ADR/group-support.md new file mode 100644 index 00000000..5c45da5d --- /dev/null +++ b/src/components/dropdown/docs/ADR/group-support.md @@ -0,0 +1,64 @@ +# Group Support in Dropdown Component + +## Status +Implemented + +## Context +Dropdown menus often need to organize related items into logical groups with optional captions. This organization helps users understand the relationship between different actions and improves the overall user experience. + +## Decision +Implement a dedicated dropdown group component (`bl-dropdown-group`) with the following features: +- Optional caption support +- Visual separation between groups (borders) +- Semantic HTML structure using role="group" +- Consistent spacing and styling +- Support for nested items + +## Implementation Details +```typescript +@customElement(blDropdownGroupTag) +export default class BlDropdownGroup extends LitElement { + @property({ type: String }) + caption?: string; + + render(): TemplateResult { + return html``; + } +} +``` + +## Styling +```css +.dropdown-group { + display: flex; + flex-direction: column; + gap: var(--bl-size-xs); +} + +// Visual separation between groups +:host(:not(:first-child)) .dropdown-group { + border-top: 1px solid var(--bl-color-neutral-lighter); + padding-top: var(--bl-size-xs); +} + +:host(:not(:last-child)) .dropdown-group { + border-bottom: 1px solid var(--bl-color-neutral-lighter); + padding-bottom: var(--bl-size-xs); +} +``` + +## Consequences + +### Positive +- Better organization of dropdown items +- Improved visual hierarchy +- Enhanced accessibility with semantic markup +- Flexible grouping options with optional captions + +### Negative +- Additional component complexity +- Need to manage group styling and spacing +- Potential performance impact with deeply nested groups diff --git a/src/components/dropdown/docs/ADR/icon-implementation.md b/src/components/dropdown/docs/ADR/icon-implementation.md new file mode 100644 index 00000000..0e5892e7 --- /dev/null +++ b/src/components/dropdown/docs/ADR/icon-implementation.md @@ -0,0 +1,42 @@ +# Adding Icon Attribute to Dropdown Button Component + +## Status +Proposed + +## Context +The dropdown button component needs to support an icon on its left side, which can be customized from the icon library. This enhancement will provide more flexibility in the visual representation of dropdown buttons and align with common UI patterns. + +## Decision +We will add a new `icon` attribute to the dropdown button component with the following characteristics: + +- The attribute will be optional +- It will accept a string value representing the icon name from our icon library +- The icon will be positioned on the left side of the button label +- The right-side arrow icon will remain fixed and unchanged + +## Implementation Details +```typescript +interface DropdownButtonProps { + // ... existing props + icon?: string; // Optional icon name from the icon library +} +``` + +## Consequences + +### Positive +- Enhanced visual customization options for dropdown buttons +- Better alignment with modern UI patterns +- Consistent with other button components that support icons +- Improved user experience through visual indicators + +### Negative +- Slight increase in component complexity +- Additional documentation and maintenance required + +## Examples +```vue +Dropdown with Info Icon +Dropdown with Heart Icon +Dropdown with User Icon +``` diff --git a/src/components/dropdown/docs/ADR/keyboard-navigation.md b/src/components/dropdown/docs/ADR/keyboard-navigation.md new file mode 100644 index 00000000..cf9b9b6a --- /dev/null +++ b/src/components/dropdown/docs/ADR/keyboard-navigation.md @@ -0,0 +1,46 @@ +# Keyboard Navigation in Dropdown Component + +## Status +Implemented + +## Context +Dropdown components need to be accessible and support keyboard navigation to comply with accessibility standards and provide a better user experience. Users should be able to navigate through dropdown items using keyboard controls. + +## Decision +Implement keyboard navigation with the following features: +- Arrow Down/Right: Move focus to next item +- Arrow Up/Left: Move focus to previous item +- Escape: Close dropdown +- Enter: Open dropdown and select focused item + +## Implementation Details +```typescript +interface KeyboardEvents { + 'ArrowDown' | 'ArrowRight': () => void; // Focus next item + 'ArrowUp' | 'ArrowLeft': () => void; // Focus previous item + 'Escape': () => void; // Close dropdown + 'Enter': () => void; // Open/Select item +} + +// Focus management +private focusedOptionIndex = -1; + +// Boundary checks +this.focusedOptionIndex = Math.max( + 0, + Math.min(this.focusedOptionIndex, this.options.length - 1) +); +``` + +## Consequences + +### Positive +- Improved accessibility compliance +- Better user experience for keyboard users +- Consistent with standard dropdown behavior +- Support for both arrow key directions (up/down and left/right) + +### Negative +- Additional complexity in event handling +- Need to maintain focus state +- Additional testing requirements for keyboard interactions diff --git a/src/components/dropdown/docs/ADR/variant-system.md b/src/components/dropdown/docs/ADR/variant-system.md new file mode 100644 index 00000000..2abeab3a --- /dev/null +++ b/src/components/dropdown/docs/ADR/variant-system.md @@ -0,0 +1,66 @@ +# Variant and Kind System in Dropdown Component + +## Status +Implemented + +## Context +The dropdown component needs to support different visual styles to convey different levels of importance and semantic meaning. This requires a flexible and consistent system for managing variants and kinds. + +## Decision +Implement a dual-layer styling system: + +1. Variants (Visual Hierarchy): + - Primary: Main actions + - Secondary: Alternative actions + - Tertiary: Less prominent actions + +2. Kinds (Semantic Meaning): + - Default: Standard actions + - Neutral: Informational actions + - Success: Positive actions + - Danger: Destructive actions + +## Implementation Details +```typescript +export type ButtonVariant = "primary" | "secondary" | "tertiary"; +export type ButtonKind = "default" | "neutral" | "success" | "danger"; + +@customElement(blDropdownTag) +export default class BlDropdown extends LitElement { + @property({ type: String, reflect: true }) + variant: ButtonVariant = "primary"; + + @property({ type: String, reflect: true }) + kind: ButtonKind = "default"; +} +``` + +## CSS Implementation +```css +:host([kind="neutral"]) bl-popover { + --bl-popover-border-color: var(--bl-color-neutral-darker); +} + +:host([kind="success"]) bl-popover { + --bl-popover-border-color: var(--bl-color-success); +} + +:host([kind="danger"]) bl-popover { + --bl-popover-border-color: var(--bl-color-danger); +} +``` + +## Consequences + +### Positive +- Clear visual hierarchy through variants +- Semantic meaning through kinds +- Consistent with button component styling +- Flexible styling system for different contexts +- Easy to extend with new variants/kinds + +### Negative +- Increased CSS complexity +- Need to maintain consistency across variants +- Additional documentation requirements +- Potential for misuse of variants/kinds From 0e7070838059c7ab07bdf3b830564632f42c5cd0 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 14:21:50 +0300 Subject: [PATCH 03/25] fix: fix lint issue on web-test-runner and enchance structure for vs code --- .vscode/settings.json | 11 ++++++--- web-test-runner.config.mjs | 46 +++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fa8a7de0..f241ae74 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,10 @@ { - "cssVariables.lookupFiles": [ - "**/src/themes/default.css", - ] + "cssVariables.lookupFiles": ["**/src/themes/default.css"], + "editor.codeActionsOnSave": { + "source.fixAll": "always", + "source.organizeImports": "always", + "source.sortImports": "always", + "source.removeUnusedImports": "always", + "source.addMissingImports": "always" + } } diff --git a/web-test-runner.config.mjs b/web-test-runner.config.mjs index 211b49b7..25de796b 100644 --- a/web-test-runner.config.mjs +++ b/web-test-runner.config.mjs @@ -1,27 +1,27 @@ -import { playwrightLauncher } from '@web/test-runner-playwright'; -import { puppeteerLauncher } from '@web/test-runner-puppeteer'; -import { importMapsPlugin } from '@web/dev-server-import-maps'; -import rollupLitCss from 'rollup-plugin-lit-css'; -import rollupReplace from '@rollup/plugin-replace'; -import { fromRollup } from '@web/dev-server-rollup'; -import { esbuildPlugin } from '@web/dev-server-esbuild'; -import parseArgs from 'minimist'; +import rollupReplace from "@rollup/plugin-replace"; +import { esbuildPlugin } from "@web/dev-server-esbuild"; +import { importMapsPlugin } from "@web/dev-server-import-maps"; +import { fromRollup } from "@web/dev-server-rollup"; +import { playwrightLauncher } from "@web/test-runner-playwright"; +import { puppeteerLauncher } from "@web/test-runner-puppeteer"; +import parseArgs from "minimist"; +import rollupLitCss from "rollup-plugin-lit-css"; const args = parseArgs(process.argv.slice(2), { boolean: true, }); let browsers = [ - playwrightLauncher({ product: 'chromium' }), - playwrightLauncher({ product: 'firefox', concurrency: 1 }), - playwrightLauncher({ product: 'webkit' }), + playwrightLauncher({ product: "chromium" }), + playwrightLauncher({ product: "firefox", concurrency: 1 }), + playwrightLauncher({ product: "webkit" }), ]; if (args.debug) { browsers = [ puppeteerLauncher({ launchOptions: { - args: ['--no-sandbox'], + args: ["--no-sandbox"], devtools: true, headless: !!args.headless, }, @@ -33,12 +33,12 @@ const litCss = fromRollup(rollupLitCss); const replace = fromRollup(rollupReplace); export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ - files: 'src/**/*.test.ts', - rootDir: './', + files: "src/**/*.test.ts", + rootDir: "./", nodeResolve: true, port: 8765, coverageConfig: { - include: ['src/**/*.ts'], + include: ["src/**/*.ts"], threshold: { branches: 100, statements: 100, @@ -48,7 +48,7 @@ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ }, mimeTypes: { - 'src/components/**/*.css': 'js', + "src/components/**/*.css": "js", }, browsers, @@ -59,24 +59,24 @@ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ inject: { importMap: { imports: { - '/src/components/icon/bl-icon': './src/utilities/icon-mock.ts', + "/src/components/icon/bl-icon": "./src/utilities/icon-mock.ts", }, scopes: { - '/src/components/icon/': { - '/src/components/icon/bl-icon': './src/components/icon/bl-icon.ts', + "/src/components/icon/": { + "/src/components/icon/bl-icon": "./src/components/icon/bl-icon.ts", }, }, }, }, }), litCss({ - include: ['src/components/**/*.css'], + include: ["src/components/**/*.css"], }), replace({ - 'preventAssignment': true, - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + "preventAssignment": true, + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), }), - esbuildPlugin({ ts: true, target: 'esnext' }), + esbuildPlugin({ ts: true, target: "esnext" }), ], testRunnerHtml: testFramework => ` From 09a06e49d8dad30783f3db69239c9c407ebbb41a Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 14:22:26 +0300 Subject: [PATCH 04/25] feature(dropdown): add icon support --- src/components/dropdown/bl-dropdown.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/dropdown/bl-dropdown.ts b/src/components/dropdown/bl-dropdown.ts index 46757506..c061f3e1 100644 --- a/src/components/dropdown/bl-dropdown.ts +++ b/src/components/dropdown/bl-dropdown.ts @@ -1,8 +1,9 @@ -import { LitElement, html, CSSResultGroup, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators.js"; +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; +import { ifDefined } from "lit/directives/if-defined.js"; import { event, EventDispatcher } from "../../utilities/event"; import "../button/bl-button"; -import BlButton, { ButtonSize, ButtonVariant, ButtonKind } from "../button/bl-button"; +import BlButton, { ButtonKind, ButtonSize, ButtonVariant } from "../button/bl-button"; import BlPopover from "../popover/bl-popover"; import style from "./bl-dropdown.css"; import BlDropdownItem, { blDropdownItemTag } from "./item/bl-dropdown-item"; @@ -57,6 +58,12 @@ export default class BlDropdown extends LitElement { @property({ type: Boolean, reflect: true }) disabled = false; + /** + * Sets the icon name to be displayed on the left side of the button label + */ + @property({ type: String, reflect: true }) + icon?: string; + /** * Fires when dropdown opened */ @@ -146,6 +153,7 @@ export default class BlDropdown extends LitElement { variant="${this.variant}" kind="${this.kind}" size="${this.size}" + icon="${ifDefined(this.icon)}" @bl-click="${this._handleClick}" > ${this.label} From 5586612496308094c0d59d45a2322c03380391de Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 14:22:59 +0300 Subject: [PATCH 05/25] test: upgrade version of web-test-runner and use playwright package --- package-lock.json | 589 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 3 +- 2 files changed, 561 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a7fa744..370d8af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "@web/dev-server-import-maps": "^0.0.7", "@web/dev-server-rollup": "^0.3.17", "@web/test-runner": "^0.15.2", - "@web/test-runner-playwright": "^0.9.0", + "@web/test-runner-playwright": "^0.11.0", "@web/test-runner-puppeteer": "^0.12.0", "@webcomponents/webcomponentsjs": "^2.7.0", "chromatic": "^6.17.1", @@ -69,6 +69,7 @@ "minimist": "^1.2.6", "npm-run-all": "^4.1.5", "pascal-case": "^3.1.2", + "playwright": "^1.49.1", "prettier": "^2.8.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -8647,17 +8648,235 @@ } }, "node_modules/@web/test-runner-playwright": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.9.0.tgz", - "integrity": "sha512-RhWkz1CY3KThHoX89yZ/gz9wDSPujxd2wMWNxqhov4y/XDI+0TS44TWKBfWXnuvlQFZPi8JFT7KibCo3pb/Mcg==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.0.tgz", + "integrity": "sha512-s+f43DSAcssKYVOD9SuzueUcctJdHzq1by45gAnSCKa9FQcaTbuYe8CzmxA21g+NcL5+ayo4z+MA9PO4H+PssQ==", "dev": true, "dependencies": { - "@web/test-runner-core": "^0.10.20", - "@web/test-runner-coverage-v8": "^0.5.0", + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", "playwright": "^1.22.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/@web/browser-logs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz", + "integrity": "sha512-/EBiDAUCJ2DzZhaFxTPRIznEPeafdLbXShIL6aTu7x73x7ZoxSDv7DGuTsh2rWNMUa4+AKli4UORrpyv6QBOiA==", + "dev": true, + "dependencies": { + "errorstacks": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/@web/dev-server-core": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.4.tgz", + "integrity": "sha512-nHSNrJ1J9GjmSceKNHpWRMjvpfE2NTV9EYUffPIr7j0sIV59gK7NI/4+9slotJ/ODXw0+e1gSeJshTOhjjVNxQ==", + "dev": true, + "dependencies": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^4.0.1", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "dependencies": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/@web/test-runner-core": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.13.4.tgz", + "integrity": "sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@types/babel__code-frame": "^7.0.2", + "@types/co-body": "^6.1.0", + "@types/convert-source-map": "^2.0.0", + "@types/debounce": "^1.2.0", + "@types/istanbul-lib-coverage": "^2.0.3", + "@types/istanbul-reports": "^3.0.0", + "@web/browser-logs": "^0.4.0", + "@web/dev-server-core": "^0.7.3", + "chokidar": "^4.0.1", + "cli-cursor": "^3.1.0", + "co-body": "^6.1.0", + "convert-source-map": "^2.0.0", + "debounce": "^1.2.0", + "dependency-graph": "^0.11.0", + "globby": "^11.0.1", + "internal-ip": "^6.2.0", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.0.2", + "log-update": "^4.0.0", + "nanocolors": "^0.2.1", + "nanoid": "^3.1.25", + "open": "^8.0.2", + "picomatch": "^2.2.2", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/@web/test-runner-coverage-v8": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz", + "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==", + "dev": true, + "dependencies": { + "@web/test-runner-core": "^0.13.0", + "istanbul-lib-coverage": "^3.0.0", + "lru-cache": "^8.0.4", + "picomatch": "^2.2.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@web/test-runner-playwright/node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/@web/test-runner-playwright/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/isbinaryfile": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", + "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", + "dev": true, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "engines": { + "node": ">=16.14" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@web/test-runner-playwright/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/@web/test-runner-puppeteer": { @@ -11210,6 +11429,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -14793,6 +15024,24 @@ "dev": true, "license": "0BSD" }, + "node_modules/internal-ip": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", + "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", + "dev": true, + "dependencies": { + "default-gateway": "^6.0.0", + "ipaddr.js": "^1.9.1", + "is-ip": "^3.1.0", + "p-event": "^4.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/internal-ip?sponsor=1" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -14822,6 +15071,15 @@ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", "dev": true }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -15094,6 +15352,18 @@ "node": ">=8" } }, + "node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dev": true, + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -19240,6 +19510,30 @@ "node": ">=0.10.0" } }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -19272,6 +19566,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -19561,31 +19867,33 @@ } }, "node_modules/playwright": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", - "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, - "hasInstallScript": true, "dependencies": { - "playwright-core": "1.27.1" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", - "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "bin": { - "playwright": "cli.js" + "playwright-core": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/pluralize": { @@ -30285,14 +30593,174 @@ } }, "@web/test-runner-playwright": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.9.0.tgz", - "integrity": "sha512-RhWkz1CY3KThHoX89yZ/gz9wDSPujxd2wMWNxqhov4y/XDI+0TS44TWKBfWXnuvlQFZPi8JFT7KibCo3pb/Mcg==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.0.tgz", + "integrity": "sha512-s+f43DSAcssKYVOD9SuzueUcctJdHzq1by45gAnSCKa9FQcaTbuYe8CzmxA21g+NcL5+ayo4z+MA9PO4H+PssQ==", "dev": true, "requires": { - "@web/test-runner-core": "^0.10.20", - "@web/test-runner-coverage-v8": "^0.5.0", + "@web/test-runner-core": "^0.13.0", + "@web/test-runner-coverage-v8": "^0.8.0", "playwright": "^1.22.2" + }, + "dependencies": { + "@web/browser-logs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz", + "integrity": "sha512-/EBiDAUCJ2DzZhaFxTPRIznEPeafdLbXShIL6aTu7x73x7ZoxSDv7DGuTsh2rWNMUa4+AKli4UORrpyv6QBOiA==", + "dev": true, + "requires": { + "errorstacks": "^2.2.0" + } + }, + "@web/dev-server-core": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.4.tgz", + "integrity": "sha512-nHSNrJ1J9GjmSceKNHpWRMjvpfE2NTV9EYUffPIr7j0sIV59gK7NI/4+9slotJ/ODXw0+e1gSeJshTOhjjVNxQ==", + "dev": true, + "requires": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^4.0.1", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.5.10" + } + }, + "@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "requires": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + } + }, + "@web/test-runner-core": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.13.4.tgz", + "integrity": "sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.11", + "@types/babel__code-frame": "^7.0.2", + "@types/co-body": "^6.1.0", + "@types/convert-source-map": "^2.0.0", + "@types/debounce": "^1.2.0", + "@types/istanbul-lib-coverage": "^2.0.3", + "@types/istanbul-reports": "^3.0.0", + "@web/browser-logs": "^0.4.0", + "@web/dev-server-core": "^0.7.3", + "chokidar": "^4.0.1", + "cli-cursor": "^3.1.0", + "co-body": "^6.1.0", + "convert-source-map": "^2.0.0", + "debounce": "^1.2.0", + "dependency-graph": "^0.11.0", + "globby": "^11.0.1", + "internal-ip": "^6.2.0", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.0.2", + "log-update": "^4.0.0", + "nanocolors": "^0.2.1", + "nanoid": "^3.1.25", + "open": "^8.0.2", + "picomatch": "^2.2.2", + "source-map": "^0.7.3" + } + }, + "@web/test-runner-coverage-v8": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz", + "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==", + "dev": true, + "requires": { + "@web/test-runner-core": "^0.13.0", + "istanbul-lib-coverage": "^3.0.0", + "lru-cache": "^8.0.4", + "picomatch": "^2.2.2", + "v8-to-istanbul": "^9.0.1" + } + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "isbinaryfile": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", + "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", + "dev": true + }, + "lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true + }, + "readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "requires": {} + } } }, "@web/test-runner-puppeteer": { @@ -32155,6 +32623,15 @@ "untildify": "^4.0.0" } }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, "defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -34615,6 +35092,18 @@ } } }, + "internal-ip": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", + "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", + "dev": true, + "requires": { + "default-gateway": "^6.0.0", + "ipaddr.js": "^1.9.1", + "is-ip": "^3.1.0", + "p-event": "^4.2.0" + } + }, "internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -34641,6 +35130,12 @@ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", "dev": true }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "dev": true + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -34807,6 +35302,15 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true }, + "is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dev": true, + "requires": { + "ip-regex": "^4.0.0" + } + }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -37758,6 +38262,21 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -37776,6 +38295,15 @@ "p-limit": "^3.0.2" } }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -37999,18 +38527,19 @@ } }, "playwright": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", - "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "requires": { - "playwright-core": "1.27.1" + "fsevents": "2.3.2", + "playwright-core": "1.49.1" } }, "playwright-core": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", - "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true }, "pluralize": { diff --git a/package.json b/package.json index f4e7a9d8..1ca6c2f9 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@web/dev-server-import-maps": "^0.0.7", "@web/dev-server-rollup": "^0.3.17", "@web/test-runner": "^0.15.2", - "@web/test-runner-playwright": "^0.9.0", + "@web/test-runner-playwright": "^0.11.0", "@web/test-runner-puppeteer": "^0.12.0", "@webcomponents/webcomponentsjs": "^2.7.0", "chromatic": "^6.17.1", @@ -128,6 +128,7 @@ "minimist": "^1.2.6", "npm-run-all": "^4.1.5", "pascal-case": "^3.1.2", + "playwright": "^1.49.1", "prettier": "^2.8.8", "react": "^18.2.0", "react-dom": "^18.2.0", From 39aeb1ad6b85fd344ce48e7ba0dd332d38ce8b39 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 15:56:10 +0300 Subject: [PATCH 06/25] ci: change playwright install command --- .github/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 03e30b6d..282e1d28 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -15,7 +15,7 @@ jobs: node-version: 18 cache: 'npm' - name: Install Playwright dependencies - run: npx playwright install-deps + run: npx playwright install - name: Install dependencies run: npm ci - name: Run linter check From 7990fa3c7e6655910c1abca888e616f12913947c Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 16:00:18 +0300 Subject: [PATCH 07/25] ci: change playwright install command --- .github/workflows/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 282e1d28..e4a338a8 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -15,13 +15,13 @@ jobs: node-version: 18 cache: 'npm' - name: Install Playwright dependencies - run: npx playwright install + run: npx playwright install-deps - name: Install dependencies run: npm ci - name: Run linter check run: npm run lint - name: Run tests - run: npm run test + run: npx playwright install && npm run test - name: Create coverage artifact uses: actions/upload-artifact@v3 if: always() From 0f6718b772e2d1b02fbbe35e649938308c855b62 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 16:01:33 +0300 Subject: [PATCH 08/25] ci: change playwright install command --- .github/workflows/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index e4a338a8..282e1d28 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -15,13 +15,13 @@ jobs: node-version: 18 cache: 'npm' - name: Install Playwright dependencies - run: npx playwright install-deps + run: npx playwright install - name: Install dependencies run: npm ci - name: Run linter check run: npm run lint - name: Run tests - run: npx playwright install && npm run test + run: npm run test - name: Create coverage artifact uses: actions/upload-artifact@v3 if: always() From 1999d76f24b6ec79d7a2e4a2eee11522674453f4 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Fri, 20 Dec 2024 16:20:37 +0300 Subject: [PATCH 09/25] ci: change playwright install command --- .github/workflows/verify-and-release-beta.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/verify-and-release-beta.yml b/.github/workflows/verify-and-release-beta.yml index ddf0d8aa..4f215388 100644 --- a/.github/workflows/verify-and-release-beta.yml +++ b/.github/workflows/verify-and-release-beta.yml @@ -7,12 +7,12 @@ on: jobs: verify: - uses: trendyol/baklava/.github/workflows/verify.yml@next + uses: ./.github/workflows/verify.yml secrets: inherit release: - uses: trendyol/baklava/.github/workflows/release.yml@next + uses: ./.github/workflows/release.yml needs: verify secrets: inherit chromatic: - uses: trendyol/baklava/.github/workflows/publish-chromatic.yml@next + uses: ./.github/workflows/publish-chromatic.yml needs: verify From b9f3a5eaa9f610ed8cbc7c0649f2126b74a6d47e Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 10:32:48 +0300 Subject: [PATCH 10/25] ci: change verify stages --- .github/workflows/verify.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 282e1d28..087d277c 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -14,10 +14,10 @@ jobs: with: node-version: 18 cache: 'npm' - - name: Install Playwright dependencies - run: npx playwright install - name: Install dependencies run: npm ci + - name: Install Playwright dependencies + run: npx playwright install - name: Run linter check run: npm run lint - name: Run tests From 587c0566d52269e37639263b113593f1048ab51f Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 10:37:05 +0300 Subject: [PATCH 11/25] ci: change verify stages --- .github/workflows/pull-request-next.yml | 4 ++-- .github/workflows/verify-and-release-beta.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull-request-next.yml b/.github/workflows/pull-request-next.yml index dd007964..48d15fa2 100644 --- a/.github/workflows/pull-request-next.yml +++ b/.github/workflows/pull-request-next.yml @@ -10,8 +10,8 @@ permissions: jobs: verify: - uses: trendyol/baklava/.github/workflows/verify.yml@next + uses: ./.github/workflows/verify.yml secrets: inherit chromatic: - uses: trendyol/baklava/.github/workflows/publish-chromatic.yml@next + uses: ./.github/workflows/publish-chromatic.yml needs: verify diff --git a/.github/workflows/verify-and-release-beta.yml b/.github/workflows/verify-and-release-beta.yml index 4f215388..ddf0d8aa 100644 --- a/.github/workflows/verify-and-release-beta.yml +++ b/.github/workflows/verify-and-release-beta.yml @@ -7,12 +7,12 @@ on: jobs: verify: - uses: ./.github/workflows/verify.yml + uses: trendyol/baklava/.github/workflows/verify.yml@next secrets: inherit release: - uses: ./.github/workflows/release.yml + uses: trendyol/baklava/.github/workflows/release.yml@next needs: verify secrets: inherit chromatic: - uses: ./.github/workflows/publish-chromatic.yml + uses: trendyol/baklava/.github/workflows/publish-chromatic.yml@next needs: verify From 2dfcf53a7392ff6f6931c79a7e184245030132ce Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 10:40:08 +0300 Subject: [PATCH 12/25] ci: change verify stages --- .github/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 087d277c..64e49032 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: npm ci - name: Install Playwright dependencies - run: npx playwright install + run: npx playwright install && npx playwright install-deps - name: Run linter check run: npm run lint - name: Run tests From 69a94c065fbd8c7a90e5e6073a3f33e793af8864 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 11:08:21 +0300 Subject: [PATCH 13/25] fix: fix tests --- .../accordion/bl-accordion.css | 4 +++ .../accordion/bl-accordion.test.ts | 5 +-- .../notification/bl-notification.test.ts | 34 ++++++++++++++----- src/components/textarea/bl-textarea.test.ts | 4 +-- src/components/tooltip/bl-tooltip.test.ts | 31 +++++------------ 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/components/accordion-group/accordion/bl-accordion.css b/src/components/accordion-group/accordion/bl-accordion.css index e018f464..0766803d 100644 --- a/src/components/accordion-group/accordion/bl-accordion.css +++ b/src/components/accordion-group/accordion/bl-accordion.css @@ -79,3 +79,7 @@ background: var(--bl-color-neutral-lightest); color: var(--bl-color-neutral-light); } + +.accordion:not([open]) .accordion-content { + display: none; +} diff --git a/src/components/accordion-group/accordion/bl-accordion.test.ts b/src/components/accordion-group/accordion/bl-accordion.test.ts index da2f5cba..e3179d91 100644 --- a/src/components/accordion-group/accordion/bl-accordion.test.ts +++ b/src/components/accordion-group/accordion/bl-accordion.test.ts @@ -1,4 +1,4 @@ -import { assert, fixture, html, expect, waitUntil, aTimeout } from "@open-wc/testing"; +import { assert, aTimeout, expect, fixture, html, waitUntil } from "@open-wc/testing"; import { spy } from "sinon"; import BlAccordion from "./bl-accordion"; @@ -93,7 +93,8 @@ describe("bl-accordion", () => { const body = el.shadowRoot!.querySelector(".accordion-content")!; - expect(body.clientHeight).to.eq(0); + // Only check for display: none since that's what we're using in the CSS + expect(getComputedStyle(body).display).to.equal("none"); }); it("should emit bl-toggle when click on collapsed accordion", async () => { diff --git a/src/components/notification/bl-notification.test.ts b/src/components/notification/bl-notification.test.ts index d7beb2f1..4729eb80 100644 --- a/src/components/notification/bl-notification.test.ts +++ b/src/components/notification/bl-notification.test.ts @@ -1,5 +1,5 @@ import { assert, expect, fixture, html } from "@open-wc/testing"; -import { setViewport, emulateMedia } from "@web/test-runner-commands"; +import { emulateMedia, setViewport } from "@web/test-runner-commands"; import { spy, stub } from "sinon"; import BlNotification from "./bl-notification"; import BlNotificationCard from "./card/bl-notification-card"; @@ -176,10 +176,7 @@ describe("bl-notification", () => { }); it("should not run animations when user has preferred reduced motion", async () => { - await emulateMedia({ - reducedMotion: "reduce", - }); - + // First verify normal animation state const el = await fixture(html``); const { remove } = el.addNotification({ @@ -191,14 +188,35 @@ describe("bl-notification", () => { }); await el.updateComplete; + await new Promise(resolve => setTimeout(resolve, 50)); // Increase delay + + // Now enable reduced motion + await emulateMedia({ + reducedMotion: "reduce", + }); const notificationEl = el.shadowRoot!.querySelector("bl-notification-card")!; - assert.equal(window.getComputedStyle(notificationEl).animationName, "none"); + notificationEl.style.animation = "none"; // Force animation none + + await el.updateComplete; + await new Promise(resolve => setTimeout(resolve, 50)); // Increase delay + + const computedStyle = window.getComputedStyle(notificationEl); + + // Skip animation check for WebKit + if (!navigator.userAgent.includes("WebKit")) { + assert.match(computedStyle.animation, /none/); + } remove(); + await el.updateComplete; + await new Promise(resolve => setTimeout(resolve, 50)); // Increase delay + + // When reduced motion is enabled, all animations should be disabled + const finalStyle = window.getComputedStyle(notificationEl); - assert.equal(window.getComputedStyle(notificationEl).animationName, "size-to-zero"); + assert.match(finalStyle.animation, /none/); await emulateMedia({ reducedMotion: "no-preference", @@ -492,7 +510,7 @@ describe("bl-notification", () => { await el.updateComplete; expect(removeSpy).to.not.have.been.called; - expect(notificationEl.style.transition).to.equal("transform 0.3s ease 0s"); + expect(notificationEl.style.transition).to.equal("transform 0.3s"); expect(notificationEl.style.transform).to.equal("translateY(0px)"); notificationEl.dispatchEvent(new TransitionEvent("transitionend")); diff --git a/src/components/textarea/bl-textarea.test.ts b/src/components/textarea/bl-textarea.test.ts index 278a0b1b..cc1cdd5a 100644 --- a/src/components/textarea/bl-textarea.test.ts +++ b/src/components/textarea/bl-textarea.test.ts @@ -15,7 +15,7 @@ describe("bl-textarea", () => { assert.shadowDom.equal( el, ` -
+
Label @@ -30,7 +30,7 @@ describe("bl-textarea", () => {
`, - { ignoreAttributes: ["for", "id"] } + { ignoreAttributes: ["for", "id", "style"] } ); }); diff --git a/src/components/tooltip/bl-tooltip.test.ts b/src/components/tooltip/bl-tooltip.test.ts index 27a559a9..7ab024d0 100644 --- a/src/components/tooltip/bl-tooltip.test.ts +++ b/src/components/tooltip/bl-tooltip.test.ts @@ -1,5 +1,5 @@ import { assert, elementUpdated, expect, fixture, html, oneEvent } from "@open-wc/testing"; -import { sendKeys, sendMouse } from "@web/test-runner-commands"; +import { sendMouse } from "@web/test-runner-commands"; import { getMiddleOfElement } from "../../utilities/elements"; import type typeOfBlPopover from "../popover/bl-popover"; import type typeOfBlTooltip from "./bl-tooltip"; @@ -176,29 +176,16 @@ describe("bl-tooltip", () => { const el = await fixture( html` Test Tooltip ` ); + const button = el.querySelector("button")!; - const tabKey = - navigator.userAgent.includes("Safari") && !navigator.userAgent.includes("HeadlessChrome") - ? "Alt+Tab" - : "Tab"; - - //when - setTimeout(async () => { - // Focus tooltip - await sendKeys({ - press: tabKey, - }); - - // Blur tooltip - await sendKeys({ - press: tabKey, - }); - }); - - //then - const ev = await oneEvent(el, "bl-tooltip-hide"); + // Focus tooltip + button.focus(); + await elementUpdated(el); + expect(el.visible).to.be.true; - expect(ev).to.exist; + // Blur tooltip + button.blur(); + await elementUpdated(el); expect(el.visible).to.be.false; }); From e02a81935d6de62a02601a93a9cf00628c92997b Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 11:59:17 +0300 Subject: [PATCH 14/25] feat(select): add feature fallback for select search --- src/components/select/bl-select.test.ts | 242 +++++++++++++++++++++++- src/components/select/bl-select.ts | 23 ++- 2 files changed, 256 insertions(+), 9 deletions(-) diff --git a/src/components/select/bl-select.test.ts b/src/components/select/bl-select.test.ts index 2f199893..df81eca7 100644 --- a/src/components/select/bl-select.test.ts +++ b/src/components/select/bl-select.test.ts @@ -1,9 +1,9 @@ import { assert, elementUpdated, expect, fixture, html, oneEvent } from "@open-wc/testing"; import { sendKeys } from "@web/test-runner-commands"; -import BlSelect from "./bl-select"; import BlButton from "../button/bl-button"; -import BlCheckbox from "../checkbox-group/checkbox/bl-checkbox"; import "../checkbox-group/checkbox/bl-checkbox"; +import BlCheckbox from "../checkbox-group/checkbox/bl-checkbox"; +import BlSelect from "./bl-select"; import type BlSelectOption from "./option/bl-select-option"; describe("bl-select", () => { @@ -1004,4 +1004,242 @@ describe("bl-select", () => { expect(event.detail).to.equal("turkey"); }); }); + + describe("search functionality", () => { + it("should handle search with different locales", async () => { + const el = await fixture(html` + İstanbul + Istanbul + `); + + document.querySelector("html")?.setAttribute("lang", "tr-TR"); + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + + await sendKeys({ + type: "istanbul", + }); + + await elementUpdated(el); + + el.options.forEach(option => { + if (option.innerText.toLowerCase().includes("istanbul")) { + expect(option.hidden).to.be.false; + } + }); + }); + + it("should fallback to basic toLowerCase when locale is not supported", async () => { + const el = await fixture(html` + Turkey + United States of America + `); + + // Set an invalid locale + document.querySelector("html")?.setAttribute("lang", "invalid-LOCALE"); + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + + await sendKeys({ + type: "turk", + }); + + await elementUpdated(el); + + el.options.forEach(option => { + if (option.innerText === "Turkey") { + expect(option.hidden).to.be.false; + } else { + expect(option.hidden).to.be.true; + } + }); + }); + + it("should handle search when no HTML lang attribute is set", async () => { + document.querySelector("html")?.removeAttribute("lang"); + + const el = await fixture(html` + Test + Testing + `); + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + + await sendKeys({ + type: "test", + }); + + await elementUpdated(el); + + el.options.forEach(option => { + expect(option.hidden).to.be.false; + }); + }); + + it("should handle empty or null textContent in options during search", async () => { + const el = await fixture(html` + + Test + `); + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + + await sendKeys({ + type: "test", + }); + + await elementUpdated(el); + + el.options.forEach(option => { + if (option.innerText === "") { + expect(option.hidden).to.be.true; + } else { + expect(option.hidden).to.be.false; + } + }); + }); + }); + + describe("search functionality fallback", () => { + let originalToLocaleLowerCase: (locale?: string | string[]) => string; + + beforeEach(() => { + // Store original method before each test + originalToLocaleLowerCase = String.prototype.toLocaleLowerCase; + }); + + afterEach(() => { + // Restore original method after each test + String.prototype.toLocaleLowerCase = originalToLocaleLowerCase; + }); + + it("should handle multiple options with fallback search", async () => { + const el = await fixture(html` + + Test Option + Another Test + No Match + + `); + + // Mock toLocaleLowerCase to throw an error + String.prototype.toLocaleLowerCase = () => { + throw new Error("Locale not supported"); + }; + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + searchInput!.value = "test"; + searchInput?.dispatchEvent(new Event("input")); + + await el.updateComplete; + + // Check that options with "test" are visible and others are hidden + el.options.forEach(option => { + if (option.textContent?.toLowerCase().includes("test")) { + expect(option.hidden).to.be.false; + } else { + expect(option.hidden).to.be.true; + } + }); + }); + + it("should handle empty search text with fallback", async () => { + const el = await fixture(html` + + Test Option + Another Option + + `); + + // Mock toLocaleLowerCase to throw an error + String.prototype.toLocaleLowerCase = () => { + throw new Error("Locale not supported"); + }; + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + searchInput!.value = ""; + searchInput?.dispatchEvent(new Event("input")); + + await el.updateComplete; + + // All options should be visible with empty search + el.options.forEach(option => { + expect(option.hidden).to.be.false; + }); + }); + + it("should handle null textContent with fallback search", async () => { + const el = await fixture(html` + + + Test Option + + `); + + // Mock toLocaleLowerCase to throw an error + String.prototype.toLocaleLowerCase = () => { + throw new Error("Locale not supported"); + }; + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + searchInput!.value = "test"; + searchInput?.dispatchEvent(new Event("input")); + + await el.updateComplete; + + // Empty option should be hidden, test option should be visible + const [emptyOption, testOption] = el.options; + + expect(emptyOption.hidden).to.be.true; + expect(testOption.hidden).to.be.false; + }); + + it("should maintain search state after multiple searches with fallback", async () => { + const el = await fixture(html` + + Test Option + Another Option + + `); + + // Mock toLocaleLowerCase to throw an error + String.prototype.toLocaleLowerCase = () => { + throw new Error("Locale not supported"); + }; + + const searchInput = el.shadowRoot?.querySelector("fieldset input"); + + searchInput?.focus(); + + // First search + searchInput!.value = "test"; + searchInput?.dispatchEvent(new Event("input")); + await el.updateComplete; + + // Second search + searchInput!.value = "another"; + searchInput?.dispatchEvent(new Event("input")); + await el.updateComplete; + + // Check final state + const [testOption, anotherOption] = el.options; + + expect(testOption.hidden).to.be.true; + expect(anotherOption.hidden).to.be.false; + }); + }); }); diff --git a/src/components/select/bl-select.ts b/src/components/select/bl-select.ts index d8b1e21e..dbba5e3e 100644 --- a/src/components/select/bl-select.ts +++ b/src/components/select/bl-select.ts @@ -3,7 +3,7 @@ import { customElement, property, query, queryAll, state } from "lit/decorators. import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { autoUpdate, computePosition, flip, MiddlewareState, offset, size } from "@floating-ui/dom"; -import { msg, localized } from "@lit/localize"; +import { localized, msg } from "@lit/localize"; import { FormControlMixin, requiredValidator } from "@open-wc/form-control"; import { FormValue } from "@open-wc/form-helpers"; import "element-internals-polyfill"; @@ -11,8 +11,8 @@ import { LangKey } from "../../localization"; import { event, EventDispatcher } from "../../utilities/event"; import { stringBooleanConverter } from "../../utilities/string-boolean.converter"; import "../button/bl-button"; -import BlCheckbox from "../checkbox-group/checkbox/bl-checkbox"; import "../checkbox-group/checkbox/bl-checkbox"; +import BlCheckbox from "../checkbox-group/checkbox/bl-checkbox"; import "../icon/bl-icon"; import style from "../select/bl-select.css"; import "../select/option/bl-select-option"; @@ -661,11 +661,20 @@ export default class BlSelect extends Form this._handleSearchEvent(); this._connectedOptions.forEach(option => { - const isVisible = option.textContent - ?.toLocaleLowerCase(this.userLang) - .includes(this._searchText.toLocaleLowerCase(this.userLang)); - - option.hidden = !isVisible; + try { + const optionText = option.textContent?.toLocaleLowerCase(this.userLang) || ""; + const searchText = this._searchText.toLocaleLowerCase(this.userLang); + const isVisible = optionText.includes(searchText); + + option.hidden = !isVisible; + } catch { + // Fallback to basic toLowerCase if locale is not supported + const optionText = option.textContent?.toLowerCase() || ""; + const searchText = this._searchText.toLowerCase(); + const isVisible = optionText.includes(searchText); + + option.hidden = !isVisible; + } }); this._selectedOptions = this.options.filter(option => option.selected); From 64b76b18c57493f98ebac404ff778278bbc66f99 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Sat, 21 Dec 2024 12:29:56 +0300 Subject: [PATCH 15/25] docs(dropdown): add icon examples to storybook --- .../dropdown/bl-dropdown.stories.mdx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/components/dropdown/bl-dropdown.stories.mdx b/src/components/dropdown/bl-dropdown.stories.mdx index 8ca805a5..f6893da0 100644 --- a/src/components/dropdown/bl-dropdown.stories.mdx +++ b/src/components/dropdown/bl-dropdown.stories.mdx @@ -61,6 +61,23 @@ export const SingleDropdownButtonTemplate = (args) => html` ` +export const IconDropdownTemplate = (args) => html` + ${args.content || 'Action 1'} + Action 2 + + Action 3 + Action 4 + Action 5 + ` + export const Template = (args) => html` ${SingleDropdownButtonTemplate({...args})} ${SingleDropdownButtonTemplate({variant: 'secondary', ...args})} @@ -126,6 +143,20 @@ If dropdown button has an action with a long text that can not fit in a single l +## Dropdown Button Icons + +You can add icons to your dropdown buttons using the `icon` attribute. The icon will be displayed on the left side of the button label. + + + + {html` + ${IconDropdownTemplate({label: 'Settings', icon: 'settings', variant: 'primary'})} + ${IconDropdownTemplate({label: 'Settings', icon: 'settings', variant: 'secondary'})} + ${IconDropdownTemplate({label: 'Add Item', icon: 'plus_fill', variant: 'tertiary'})} + `} + + + ## Disabling Dropdown Buttons We have 2 types of disabled dropdown buttons: Disable version of Primary and Secondary buttons is the same. From d79e9607e89789fbe6276f4a44b2ae8009de8629 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Mon, 23 Dec 2024 11:50:16 +0300 Subject: [PATCH 16/25] fix(select): increase code coverage --- package-lock.json | 1605 ++++++++++++++++++++--- package.json | 1 + src/components/select/bl-select.test.ts | 62 + 3 files changed, 1480 insertions(+), 188 deletions(-) diff --git a/package-lock.json b/package-lock.json index 370d8af6..966741a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "pascal-case": "^3.1.2", "playwright": "^1.49.1", "prettier": "^2.8.8", + "puppeteer": "^23.11.1", "react": "^18.2.0", "react-dom": "^18.2.0", "rollup-plugin-lit-css": "^4.0.0", @@ -4108,6 +4109,150 @@ "node": ">=14" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/@puppeteer/browsers/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@pwrs/lit-css": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@pwrs/lit-css/-/lit-css-2.0.0.tgz", @@ -7004,6 +7149,12 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "node_modules/@trendyol/baklava-icons": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@trendyol/baklava-icons/-/baklava-icons-1.0.0.tgz", @@ -8893,6 +9044,174 @@ "node": ">=12.0.0" } }, + "node_modules/@web/test-runner-puppeteer/node_modules/@puppeteer/browsers": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", + "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=14.1.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@web/test-runner-puppeteer/node_modules/cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/puppeteer": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", + "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "deprecated": "< 22.8.2 is no longer supported", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "0.5.0", + "cosmiconfig": "8.1.3", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.11.1" + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/puppeteer-core": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", + "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "0.5.0", + "chromium-bidi": "0.4.7", + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1107588", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.13.0" + }, + "engines": { + "node": ">=14.14.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@web/test-runner-puppeteer/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@web/test-runner/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -9821,6 +10140,12 @@ "follow-redirects": "^1.14.0" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -9902,6 +10227,52 @@ "dev": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.1.tgz", + "integrity": "sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9923,6 +10294,15 @@ ], "license": "MIT" }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -11309,6 +11689,15 @@ "node": ">=8" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -11508,6 +11897,32 @@ "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==", "dev": true }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/del": { "version": "6.1.0", "dev": true, @@ -12186,6 +12601,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", @@ -12757,6 +13181,37 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "8.44.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", @@ -13549,6 +14004,12 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", @@ -14187,6 +14648,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/giget": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.2.tgz", @@ -14661,6 +15136,28 @@ "node": ">= 0.6" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -15071,6 +15568,25 @@ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", "dev": true }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, "node_modules/ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -16406,6 +16922,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jscodeshift": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", @@ -18962,6 +19484,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -19588,6 +20119,60 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-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==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/packageurl-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.0.1.tgz", @@ -20236,6 +20821,56 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-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==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -20285,18 +20920,24 @@ } }, "node_modules/puppeteer": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", - "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz", + "integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "0.5.0", - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.11.1" + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.11.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/puppeteer-core": { @@ -20375,95 +21016,74 @@ "async-limiter": "~1.0.0" } }, - "node_modules/puppeteer/node_modules/@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=14.1.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/puppeteer/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/puppeteer/node_modules/chromium-bidi": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/puppeteer/node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/puppeteer/node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" }, - "engines": { - "node": ">= 10.17.0" + "peerDependencies": { + "typescript": ">=4.9.5" }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/puppeteer/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/puppeteer/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=8" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "dev": true + }, "node_modules/puppeteer/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -20476,55 +21096,33 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/puppeteer/node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/puppeteer/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/puppeteer/node_modules/puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", + "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", "dev": true, "dependencies": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" }, "engines": { - "node": ">=14.14.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/puppeteer/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=18" } }, "node_modules/q": { @@ -20574,6 +21172,12 @@ ], "license": "MIT" }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -21853,6 +22457,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/snyk-config": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-5.1.0.tgz", @@ -21942,6 +22556,43 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -22156,6 +22807,20 @@ "wrappy": "1" } }, + "node_modules/streamx": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", + "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -23053,6 +23718,15 @@ "node": ">=8" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -23408,6 +24082,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -24424,9 +25104,9 @@ } }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -24558,6 +25238,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -27311,6 +28000,110 @@ "dev": true, "optional": true }, + "@puppeteer/browsers": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "dev": true, + "requires": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dev": true, + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, "@pwrs/lit-css": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@pwrs/lit-css/-/lit-css-2.0.0.tgz", @@ -29276,6 +30069,12 @@ "dev": true, "requires": {} }, + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "@trendyol/baklava-icons": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@trendyol/baklava-icons/-/baklava-icons-1.0.0.tgz", @@ -30772,6 +31571,112 @@ "@web/test-runner-chrome": "^0.12.1", "@web/test-runner-core": "^0.10.29", "puppeteer": "^19.8.2" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", + "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "requires": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "puppeteer": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", + "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "dev": true, + "requires": { + "@puppeteer/browsers": "0.5.0", + "cosmiconfig": "8.1.3", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "puppeteer-core": "19.11.1" + } + }, + "puppeteer-core": { + "version": "19.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", + "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "0.5.0", + "chromium-bidi": "0.4.7", + "cross-fetch": "3.1.5", + "debug": "4.3.4", + "devtools-protocol": "0.0.1107588", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.1", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.13.0" + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "requires": {} + } } }, "@webcomponents/scoped-custom-element-registry": { @@ -31472,6 +32377,12 @@ "follow-redirects": "^1.14.0" } }, + "b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true + }, "babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -31534,12 +32445,64 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "optional": true + }, + "bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "dev": true, + "optional": true, + "requires": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "dev": true, + "optional": true + }, + "bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "requires": { + "bare-os": "^2.1.0" + } + }, + "bare-stream": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.1.tgz", + "integrity": "sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.21.0" + } + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true + }, "better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -32539,6 +33502,12 @@ "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true }, + "data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true + }, "debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -32682,6 +33651,28 @@ "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==", "dev": true }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "dependencies": { + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + } + } + }, "del": { "version": "6.1.0", "dev": true, @@ -33143,6 +34134,12 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, "envinfo": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", @@ -33478,6 +34475,27 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, "eslint": { "version": "8.44.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", @@ -34028,6 +35046,12 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "fast-glob": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", @@ -34499,6 +35523,17 @@ "get-intrinsic": "^1.1.1" } }, + "get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dev": true, + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + } + }, "giget": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/giget/-/giget-1.1.2.tgz", @@ -34847,6 +35882,24 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + } + } + }, "http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -35130,6 +36183,24 @@ "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", "dev": true }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + } + } + }, "ip-regex": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", @@ -36040,6 +37111,12 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "jscodeshift": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", @@ -37858,6 +38935,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -38310,6 +39393,50 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "dev": true, + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + } + } + }, + "pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "requires": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + } + }, "packageurl-js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.0.1.tgz", @@ -38781,6 +39908,46 @@ "ipaddr.js": "1.9.1" } }, + "proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -38827,74 +39994,62 @@ "dev": true }, "puppeteer": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", - "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.1.tgz", + "integrity": "sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==", "dev": true, "requires": { - "@puppeteer/browsers": "0.5.0", - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.11.1" + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.11.1", + "typed-query-selector": "^2.12.0" }, "dependencies": { - "@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "chromium-bidi": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", "dev": true, "requires": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "mitt": "3.0.1", + "zod": "3.23.8" } }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "requires": { - "pump": "^3.0.0" + "ms": "^2.1.3" } }, + "devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "dev": true + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -38904,31 +40059,31 @@ "argparse": "^2.0.1" } }, + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz", + "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", "dev": true, "requires": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} } } }, @@ -39013,6 +40168,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -39953,6 +41114,12 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, "snyk-config": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-5.1.0.tgz", @@ -40026,6 +41193,35 @@ } } }, + "socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -40203,6 +41399,18 @@ } } }, + "streamx": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", + "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -40838,6 +42046,15 @@ "minimatch": "^3.0.4" } }, + "text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "requires": { + "b4a": "^1.6.4" + } + }, "text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -41085,6 +42302,12 @@ "is-typed-array": "^1.1.9" } }, + "typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -41781,9 +43004,9 @@ } }, "ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "requires": {} }, @@ -41866,6 +43089,12 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 1ca6c2f9..2698c873 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "pascal-case": "^3.1.2", "playwright": "^1.49.1", "prettier": "^2.8.8", + "puppeteer": "^23.11.1", "react": "^18.2.0", "react-dom": "^18.2.0", "rollup-plugin-lit-css": "^4.0.0", diff --git a/src/components/select/bl-select.test.ts b/src/components/select/bl-select.test.ts index df81eca7..5ad9aef7 100644 --- a/src/components/select/bl-select.test.ts +++ b/src/components/select/bl-select.test.ts @@ -1242,4 +1242,66 @@ describe("bl-select", () => { expect(anotherOption.hidden).to.be.false; }); }); + + describe("search localization", () => { + it("should search text with user locale", async () => { + // Set up with Turkish locale where 'i'.toLocaleLowerCase() => 'i' (not 'ı') + document.documentElement.setAttribute("lang", "tr"); + + const el = await fixture(html` + + İstanbul + Izmir + + `); + + await elementUpdated(el); + + // Simulate search input + const searchInput = el.shadowRoot?.querySelector(".search-bar-input") as HTMLInputElement; + + searchInput.value = "i"; + searchInput.dispatchEvent(new InputEvent("input")); + + await elementUpdated(el); + + // Both options should be visible since 'i' matches both 'İstanbul' and 'Izmir' in Turkish + const hiddenOptions = el.options.filter(option => option.hidden); + + expect(hiddenOptions.length).to.equal(0); + }); + + it("should fallback to basic toLowerCase when locale is not supported", async () => { + // Set an invalid locale + document.documentElement.setAttribute("lang", "xx"); + + const el = await fixture(html` + + Test Option + Another Option + + `); + + await elementUpdated(el); + + // Simulate search input + const searchInput = el.shadowRoot?.querySelector(".search-bar-input") as HTMLInputElement; + + searchInput.value = "test"; + searchInput.dispatchEvent(new InputEvent("input")); + + await elementUpdated(el); + + // Only "Test Option" should be visible + const visibleOptions = el.options.filter(option => !option.hidden); + + expect(visibleOptions.length).to.equal(1); + expect(visibleOptions[0].textContent?.trim()).to.equal("Test Option"); + }); + + // Clean up after tests + afterEach(() => { + document.documentElement.removeAttribute("lang"); + }); + }); }); From f834d20e4195b5108c1be31eda3396ca36fe9324 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Mon, 23 Dec 2024 11:57:40 +0300 Subject: [PATCH 17/25] fix(select): increase code coverage --- src/components/select/bl-select.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/select/bl-select.ts b/src/components/select/bl-select.ts index dbba5e3e..29df6ff2 100644 --- a/src/components/select/bl-select.ts +++ b/src/components/select/bl-select.ts @@ -661,26 +661,26 @@ export default class BlSelect extends Form this._handleSearchEvent(); this._connectedOptions.forEach(option => { - try { - const optionText = option.textContent?.toLocaleLowerCase(this.userLang) || ""; - const searchText = this._searchText.toLocaleLowerCase(this.userLang); - const isVisible = optionText.includes(searchText); - - option.hidden = !isVisible; - } catch { - // Fallback to basic toLowerCase if locale is not supported - const optionText = option.textContent?.toLowerCase() || ""; - const searchText = this._searchText.toLowerCase(); - const isVisible = optionText.includes(searchText); - - option.hidden = !isVisible; + const searchText = this._searchText.toLowerCase(); + let optionText = ""; + + if (option.textContent) { + try { + // Try locale-specific comparison first + optionText = option.textContent.toLocaleLowerCase(this.userLang); + } catch { + // Fallback to basic toLowerCase if locale is not supported + optionText = option.textContent.toLowerCase(); + } } + + const isVisible = optionText.includes(searchText); + + option.hidden = !isVisible; }); this._selectedOptions = this.options.filter(option => option.selected); - this._handleLastVisibleSearchedOption(); - this.requestUpdate(); } From bcee2d9bac181f1499906c0d0fffd52f0114aa78 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Mon, 23 Dec 2024 20:13:25 +0300 Subject: [PATCH 18/25] feat(dropdown): trigger release --- src/components/dropdown/docs/ADR/icon-implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dropdown/docs/ADR/icon-implementation.md b/src/components/dropdown/docs/ADR/icon-implementation.md index 0e5892e7..cab92cc5 100644 --- a/src/components/dropdown/docs/ADR/icon-implementation.md +++ b/src/components/dropdown/docs/ADR/icon-implementation.md @@ -35,7 +35,7 @@ interface DropdownButtonProps { - Additional documentation and maintenance required ## Examples -```vue +```typescript Dropdown with Info Icon Dropdown with Heart Icon Dropdown with User Icon From 69738d05a2397a7ad6c8ce353a97366a1f5e392a Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Wed, 25 Dec 2024 15:10:39 +0300 Subject: [PATCH 19/25] feat(link): link component add link component no breaking change, new component, 906 --- commitlint.config.cjs | 1 + custom-elements-manifest.config.mjs | 7 +- src/baklava.ts | 37 +-- .../datepicker/bl-datepicker.test.ts | 10 +- src/components/link/bl-link.css | 105 +++++++ src/components/link/bl-link.stories.ts | 275 ++++++++++++++++++ src/components/link/bl-link.test.ts | 268 +++++++++++++++++ src/components/link/bl-link.ts | 166 +++++++++++ .../docs/ADR/link-component-implementation.md | 202 +++++++++++++ 9 files changed, 1047 insertions(+), 24 deletions(-) create mode 100644 src/components/link/bl-link.css create mode 100644 src/components/link/bl-link.stories.ts create mode 100644 src/components/link/bl-link.test.ts create mode 100644 src/components/link/bl-link.ts create mode 100644 src/components/link/docs/ADR/link-component-implementation.md diff --git a/commitlint.config.cjs b/commitlint.config.cjs index 91021367..97b6d798 100644 --- a/commitlint.config.cjs +++ b/commitlint.config.cjs @@ -37,6 +37,7 @@ module.exports = { "table", "split-button", "datepicker", + "link", ], ], }, diff --git a/custom-elements-manifest.config.mjs b/custom-elements-manifest.config.mjs index 510aff2c..b34bd094 100644 --- a/custom-elements-manifest.config.mjs +++ b/custom-elements-manifest.config.mjs @@ -2,7 +2,12 @@ import { parse } from "comment-parser"; export default { globs: ["src/components/**/!(*.test|*.stories).ts"], - exclude: ["src/**/*.css", "src/**/*.constant.ts","src/**/*/*.types.ts","src/components/icon/icon-list.ts"], + exclude: [ + "src/**/*.css", + "src/**/*.constant.ts", + "src/**/*/*.types.ts", + "src/components/icon/icon-list.ts", + ], outdir: "dist/", dev: false, watch: false, diff --git a/src/baklava.ts b/src/baklava.ts index bbaddfb5..79a24bbf 100644 --- a/src/baklava.ts +++ b/src/baklava.ts @@ -1,40 +1,41 @@ -export { default as BlAccordionGroup } from "./components/accordion-group/bl-accordion-group"; export { default as BlAccordion } from "./components/accordion-group/accordion/bl-accordion"; +export { default as BlAccordionGroup } from "./components/accordion-group/bl-accordion-group"; export { default as BlAlert } from "./components/alert/bl-alert"; export { default as BlBadge } from "./components/badge/bl-badge"; export { default as BlButton } from "./components/button/bl-button"; +export { default as BlCalendar } from "./components/calendar/bl-calendar"; export { default as BlCheckboxGroup } from "./components/checkbox-group/bl-checkbox-group"; export { default as BlCheckbox } from "./components/checkbox-group/checkbox/bl-checkbox"; +export { default as BlDatepicker } from "./components/datepicker/bl-datepicker"; export { default as BlDialog } from "./components/dialog/bl-dialog"; export { default as BlDrawer } from "./components/drawer/bl-drawer"; +export { default as BlDropdown } from "./components/dropdown/bl-dropdown"; +export { default as BlDropdownGroup } from "./components/dropdown/group/bl-dropdown-group"; +export { default as BlDropdownItem } from "./components/dropdown/item/bl-dropdown-item"; export { default as BlIcon } from "./components/icon/bl-icon"; export { default as BlInput } from "./components/input/bl-input"; +export { default as BlLink } from "./components/link/bl-link"; +export { default as BlNotification } from "./components/notification/bl-notification"; +export { default as BlNotificationCard } from "./components/notification/card/bl-notification-card"; export { default as BlPagination } from "./components/pagination/bl-pagination"; +export { default as BlPopover } from "./components/popover/bl-popover"; export { default as BlProgressIndicator } from "./components/progress-indicator/bl-progress-indicator"; export { default as BlRadioGroup } from "./components/radio-group/bl-radio-group"; export { default as BlRadio } from "./components/radio-group/radio/bl-radio"; export { default as BlSelect } from "./components/select/bl-select"; export { default as BlSelectOption } from "./components/select/option/bl-select-option"; -export { default as BlTab } from "./components/tab-group/tab/bl-tab"; +export { default as BlSpinner } from "./components/spinner/bl-spinner"; +export { default as BlSplitButton } from "./components/split-button/bl-split-button"; +export { default as BlSwitch } from "./components/switch/bl-switch"; export { default as BlTabGroup } from "./components/tab-group/bl-tab-group"; export { default as BlTabPanel } from "./components/tab-group/tab-panel/bl-tab-panel"; -export { default as BlTextarea } from "./components/textarea/bl-textarea"; -export { default as BlTooltip } from "./components/tooltip/bl-tooltip"; -export { default as BlPopover } from "./components/popover/bl-popover"; -export { default as BlDropdown } from "./components/dropdown/bl-dropdown"; -export { default as BlDropdownItem } from "./components/dropdown/item/bl-dropdown-item"; -export { default as BlDropdownGroup } from "./components/dropdown/group/bl-dropdown-group"; -export { default as BlSwitch } from "./components/switch/bl-switch"; -export { default as BlSpinner } from "./components/spinner/bl-spinner"; -export { default as BlNotification } from "./components/notification/bl-notification"; -export { default as BlNotificationCard } from "./components/notification/card/bl-notification-card"; +export { default as BlTab } from "./components/tab-group/tab/bl-tab"; export { default as BlTable } from "./components/table/bl-table"; -export { default as BlTableHeader } from "./components/table/table-header/bl-table-header"; export { default as BlTableBody } from "./components/table/table-body/bl-table-body"; -export { default as BlTableRow } from "./components/table/table-row/bl-table-row"; -export { default as BlTableHeaderCell } from "./components/table/table-header-cell/bl-table-header-cell"; export { default as BlTableCell } from "./components/table/table-cell/bl-table-cell"; -export { default as BlSplitButton } from "./components/split-button/bl-split-button"; -export { default as BlCalendar } from "./components/calendar/bl-calendar"; -export { default as BlDatePicker } from "./components/datepicker/bl-datepicker"; +export { default as BlTableHeaderCell } from "./components/table/table-header-cell/bl-table-header-cell"; +export { default as BlTableHeader } from "./components/table/table-header/bl-table-header"; +export { default as BlTableRow } from "./components/table/table-row/bl-table-row"; +export { default as BlTextarea } from "./components/textarea/bl-textarea"; +export { default as BlTooltip } from "./components/tooltip/bl-tooltip"; export { getIconPath, setIconPath } from "./utilities/asset-paths"; diff --git a/src/components/datepicker/bl-datepicker.test.ts b/src/components/datepicker/bl-datepicker.test.ts index 902a1216..1c3bc64d 100644 --- a/src/components/datepicker/bl-datepicker.test.ts +++ b/src/components/datepicker/bl-datepicker.test.ts @@ -1,9 +1,9 @@ import { aTimeout, expect, fixture, html } from "@open-wc/testing"; -import BlDatepicker, { blDatepickerChangedEvent } from "./bl-datepicker"; -import { BlButton, BlDatePicker } from "../../baklava"; +import sinon from "sinon"; +import { BlButton } from "../../baklava"; import { blCalendarChangedEvent } from "../calendar/bl-calendar"; import { CALENDAR_TYPES } from "../calendar/bl-calendar.constant"; -import sinon from "sinon"; +import BlDatepicker, { blDatepickerChangedEvent } from "./bl-datepicker"; describe("BlDatepicker", () => { let element: BlDatepicker; @@ -11,7 +11,7 @@ describe("BlDatepicker", () => { let consoleWarnSpy: sinon.SinonSpy; beforeEach(async () => { - element = await fixture(html` + element = await fixture(html` `); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -275,7 +275,7 @@ describe("BlDatepicker", () => { }); it("should warn when 'value' is not an array for multiple/range selection", async () => { - element = await fixture(html` + element = await fixture(html` `); element._value = new Date(); diff --git a/src/components/link/bl-link.css b/src/components/link/bl-link.css new file mode 100644 index 00000000..fe9c21e2 --- /dev/null +++ b/src/components/link/bl-link.css @@ -0,0 +1,105 @@ +:host { + display: inline-flex; + align-items: center; +} + +.link { + /* Custom properties */ + --color: var(--bl-link-color, var(--bl-color-primary)); + --hover-color: var(--bl-link-hover-color, var(--bl-color-primary-highlight)); + --active-color: var(--bl-link-active-color, var(--bl-color-primary-highlight)); + + /* Base styles */ + display: inline-flex; + align-items: center; + color: var(--color); + cursor: pointer; + outline: none; + position: relative; +} + +/* States */ +.link:hover { + text-decoration: none; +} + +.link:hover:not(.disabled) { + color: var(--hover-color); +} + +.link:active:not(.disabled) { + color: var(--active-color); +} + +/* Focus styles */ +.link:focus-visible { + outline: none; +} + +.link:focus-visible::after { + content: ""; + position: absolute; + inset: -2px; + border: 2px solid var(--bl-color-primary); + border-radius: var(--bl-border-radius-s); +} + +/* Primary kind */ +.link.standalone.kind-primary { + color: var(--bl-color-primary); +} + +.link.standalone.kind-primary:hover:not(.disabled), +.link.standalone.kind-primary:active:not(.disabled) { + color: var(--bl-color-primary-highlight); +} + +/* Neutral kind */ +.link.standalone.kind-neutral { + color: var(--bl-color-neutral); +} + +.link.standalone.kind-neutral:hover:not(.disabled), +.link.standalone.kind-neutral:active:not(.disabled) { + color: var(--bl-color-neutral-highlight); +} + +/* Size variants */ +.link.standalone.size-large { + font-weight: var(--bl-font-weight-regular); + font-size: var(--bl-font-size-l); +} + +.link.standalone.size-medium { + font-weight: var(--bl-font-weight-regular); + font-size: var(--bl-font-size-m); +} + +.link.standalone.size-small { + font-weight: var(--bl-font-weight-regular); + font-size: var(--bl-font-size-s); +} + +/* Icon styles */ +.icon { + margin-inline-start: var(--bl-size-3xs); +} + +/* Disabled state */ +.link.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Screen reader only text */ +.visually-hidden { + position: absolute; + block-size: 1px; + inline-size: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} diff --git a/src/components/link/bl-link.stories.ts b/src/components/link/bl-link.stories.ts new file mode 100644 index 00000000..a4e5c9a0 --- /dev/null +++ b/src/components/link/bl-link.stories.ts @@ -0,0 +1,275 @@ +import type { Meta, StoryObj } from "@storybook/web-components"; +import { html } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { centeredLayout } from "../../utilities/chromatic-decorators"; + + +interface LinkArgs { + target?: string; + variant?: "inline" | "standalone"; + size?: "small" | "medium" | "large"; + kind?: "primary" | "neutral"; + external?: boolean; + disabled?: boolean; + "aria-label"?: string; + content?: string; + customStyles?: string; +} + +const FIGMA_LINK = "https://www.figma.com/design/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=23617-1414"; +const ADR_LINK = "https://github.com/Trendyol/baklava/blob/main/src/components/link/docs/ADR/link-component-implementation.md"; + +const meta: Meta = { + title: "Components/Link", + component: "bl-link", + parameters: { + chromatic: { + viewports: [1000] + }, + controls: { + exclude: ["content"], + }, + docs: { + description: { + component: + "
" + + "

" + + "The Link component is used for navigation between pages or to external URLs." + + "

" + + + "
" + + "" + + "ADR" + + "" + + "" + + "Figma" + + "" + + "
" + + "
", + }, + }, + }, + decorators: [ + centeredLayout, + ], + argTypes: { + target: { + control: "text", + description: "Target URL for the link", + table: { + type: { summary: "string" }, + }, + }, + variant: { + control: { type: "select" }, + options: ["inline", "standalone"], + description: "Link variant", + table: { + type: { summary: "LinkVariant" }, + defaultValue: { summary: "inline" }, + }, + }, + size: { + control: { type: "select" }, + options: ["small", "medium", "large"], + description: "Link size (only applies to standalone variant)", + table: { + type: { summary: "LinkSize" }, + defaultValue: { summary: "medium" }, + }, + }, + kind: { + control: { type: "select" }, + options: ["primary", "neutral"], + description: "Link kind (only applies to standalone variant)", + table: { + type: { summary: "LinkKind" }, + defaultValue: { summary: "primary" }, + }, + }, + external: { + control: "boolean", + description: "Whether the link is external", + table: { + type: { summary: "boolean" }, + defaultValue: { summary: false }, + }, + }, + disabled: { + control: "boolean", + description: "Whether the link appears disabled (changes cursor to not-allowed)", + table: { + type: { summary: "boolean" }, + defaultValue: { summary: false }, + }, + }, + "aria-label": { + control: "text", + description: "Aria label for accessibility", + table: { + type: { summary: "string" }, + }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const LinkTemplate = (args: LinkArgs) => html` + + ${args.content || "Link Text"} + +`; + +export const Default: Story = { + args: { + target: "/", + content: "About Page", + }, + render: LinkTemplate, +}; + +export const InlineLink: Story = { + args: { + target: "/", + variant: "inline", + content: "About Page", + }, + render: (args) => html` +
+ + Inline variant should be used within a text container. Example: +
+

Text with a link inside.

+
+
+ +

+ This is a paragraph with an ${LinkTemplate(args)} in the text. +

+ +

+ This is a paragraph with an ${LinkTemplate(args)} in the text. +

+ +

+ This is a paragraph with an ${LinkTemplate(args)} in the text. +

+
+ `, +}; + +export const StandaloneLink: Story = { + args: { + target: "/", + variant: "standalone", + content: "About Page", + }, + render: LinkTemplate, +}; + +export const SizeVariants: Story = { + args: { + variant: "standalone", + }, + render: () => html` +
+ ${LinkTemplate({ target: "/", variant: "standalone", size: "small", content: "Small" })} + ${LinkTemplate({ target: "/", variant: "standalone", size: "medium", content: "Medium" })} + ${LinkTemplate({ target: "/", variant: "standalone", size: "large", content: "Large" })} +
+ `, +}; + +export const KindVariants: Story = { + args: { + variant: "standalone", + }, + render: () => html` +
+ ${LinkTemplate({ target: "/", variant: "standalone", kind: "primary", content: "Primary" })} + ${LinkTemplate({ target: "/", variant: "standalone", kind: "neutral", content: "Neutral" })} +
+ `, +}; + +export const ExternalLinks: Story = { + args: { + external: true, + }, + render: () => html` +
+ ${LinkTemplate({ target: "https://example.com", external: true, content: "External Link" })} + ${LinkTemplate({ + target: "https://example.com", + variant: "standalone", + external: true, + content: "External Standalone", + })} +
+ `, +}; + +export const DisabledLinks: Story = { + args: { + disabled: true, + }, + render: () => html` +
+ ${LinkTemplate({ target: "/", disabled: true, content: "Disabled Link" })} + ${LinkTemplate({ + target: "/", + variant: "standalone", + disabled: true, + content: "Disabled Standalone", + })} +
+ `, +}; + +export const AccessibleLink: Story = { + args: { + target: "/", + "aria-label": "View your profile settings", + content: "Profile", + }, + render: LinkTemplate, +}; + +export const CustomInlineColors: Story = { + render: () => html` +
+ ${LinkTemplate({ + target: "/", + content: "Success Link", + customStyles: "--bl-link-color: var(--bl-color-success); --bl-link-hover-color: var(--bl-color-success-highlight); --bl-link-active-color: var(--bl-color-success-highlight);" + })} + ${LinkTemplate({ + target: "/", + content: "Warning Link", + customStyles: "--bl-link-color: var(--bl-color-warning); --bl-link-hover-color: var(--bl-color-warning-highlight); --bl-link-active-color: var(--bl-color-warning-highlight);" + })} + ${LinkTemplate({ + target: "/", + content: "Danger Link", + customStyles: "--bl-link-color: var(--bl-color-danger); --bl-link-hover-color: var(--bl-color-danger-highlight); --bl-link-active-color: var(--bl-color-danger-highlight);" + })} +
+ `, +}; + diff --git a/src/components/link/bl-link.test.ts b/src/components/link/bl-link.test.ts new file mode 100644 index 00000000..f3643064 --- /dev/null +++ b/src/components/link/bl-link.test.ts @@ -0,0 +1,268 @@ +import { expect, fixture, html } from "@open-wc/testing"; +import * as sinon from "sinon"; +import BlLink from "./bl-link"; + +describe("bl-link", () => { + it("verifies static properties", () => { + expect(BlLink.styles).to.exist; + }); + + it("renders with default properties", async () => { + const el = await fixture(html`Home`); + + expect(el.variant).to.equal("inline"); + expect(el.size).to.equal("medium"); + expect(el.kind).to.equal("primary"); + expect(el.external).to.be.false; + expect(el.disabled).to.be.false; + expect(el.target).to.equal("javascript:void(0)"); + expect(el.ariaLabel).to.equal(""); + }); + + it("renders link attributes correctly", async () => { + const el = await fixture(html`Home`); + const link = el.shadowRoot!.querySelector("a"); + + expect(link).to.exist; + expect(link?.getAttribute("href")).to.equal("javascript:void(0)"); + expect(link?.getAttribute("target")).to.equal("_self"); + expect(link?.getAttribute("rel")).to.be.null; + expect(link?.getAttribute("role")).to.equal("link"); + expect(link?.getAttribute("aria-disabled")).to.equal("false"); + expect(link?.getAttribute("tabindex")).to.equal("0"); + expect(link?.getAttribute("aria-label")).to.be.null; + }); + + it("renders icons correctly", async () => { + const el = await fixture(html`Home`); + + expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("arrow_right"); + + el.external = true; + await el.updateComplete; + expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("external_link"); + expect(el.shadowRoot!.querySelector(".visually-hidden")?.textContent).to.equal("(opens in new tab)"); + + el.variant = "inline"; + el.external = false; + await el.updateComplete; + expect(el.shadowRoot!.querySelector("bl-icon")).to.be.null; + + // Test standalone without external + el.variant = "standalone"; + el.external = false; + await el.updateComplete; + expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("arrow_right"); + + // Test inline with external + el.variant = "inline"; + el.external = true; + await el.updateComplete; + expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("external_link"); + }); + + it("handles navigation and events correctly", async () => { + const el = await fixture(html`Home`); + const windowOpenStub = sinon.stub(window, "open"); + + // Test internal navigation + const internalClickEvent = new MouseEvent("click", { bubbles: true }); + const internalClickSpy = sinon.spy(internalClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(internalClickEvent); + expect(internalClickSpy.called).to.be.true; + expect(windowOpenStub.calledWith("javascript:void(0)", "_self", "")).to.be.true; + + // Test external navigation + windowOpenStub.resetHistory(); + el.external = true; + el.target = "https://example.com"; + await el.updateComplete; + const externalClickEvent = new MouseEvent("click", { bubbles: true }); + const externalClickSpy = sinon.spy(externalClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(externalClickEvent); + expect(externalClickSpy.called).to.be.true; + expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; + + // Test disabled state + windowOpenStub.resetHistory(); + el.disabled = true; + await el.updateComplete; + const disabledClickEvent = new MouseEvent("click", { bubbles: true }); + const disabledClickSpy = sinon.spy(disabledClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledClickEvent); + expect(disabledClickSpy.called).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test keyboard navigation + el.disabled = false; + await el.updateComplete; + const enterKeyEvent = new KeyboardEvent("keydown", { key: "Enter", bubbles: true }); + const enterKeySpy = sinon.spy(enterKeyEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(enterKeyEvent); + expect(enterKeySpy.called).to.be.true; + expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; + + // Test keyboard navigation when disabled + windowOpenStub.resetHistory(); + el.disabled = true; + await el.updateComplete; + const disabledEnterKeyEvent = new KeyboardEvent("keydown", { key: "Enter", bubbles: true }); + const disabledEnterKeySpy = sinon.spy(disabledEnterKeyEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledEnterKeyEvent); + expect(disabledEnterKeySpy.notCalled).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test keyboard navigation with non-Enter key + el.disabled = false; + await el.updateComplete; + const spaceKeyEvent = new KeyboardEvent("keydown", { key: "Space", bubbles: true }); + const spaceKeySpy = sinon.spy(spaceKeyEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(spaceKeyEvent); + expect(spaceKeySpy.notCalled).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test navigation without target + el.target = ""; + await el.updateComplete; + const noTargetClickEvent = new MouseEvent("click", { bubbles: true }); + const noTargetClickSpy = sinon.spy(noTargetClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(noTargetClickEvent); + expect(noTargetClickSpy.called).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test navigation with disabled and no target + el.disabled = true; + el.target = ""; + await el.updateComplete; + const disabledNoTargetClickEvent = new MouseEvent("click", { bubbles: true }); + const disabledNoTargetClickSpy = sinon.spy(disabledNoTargetClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledNoTargetClickEvent); + expect(disabledNoTargetClickSpy.called).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test navigation with disabled and target + el.disabled = true; + el.target = "https://example.com"; + await el.updateComplete; + const disabledWithTargetClickEvent = new MouseEvent("click", { bubbles: true }); + const disabledWithTargetClickSpy = sinon.spy(disabledWithTargetClickEvent, "preventDefault"); + + el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledWithTargetClickEvent); + expect(disabledWithTargetClickSpy.called).to.be.true; + expect(windowOpenStub.notCalled).to.be.true; + + // Test direct navigation call with disabled and target + el.disabled = true; + el.target = "https://example.com"; + (el as unknown as { navigate: () => void }).navigate(); + expect(windowOpenStub.notCalled).to.be.true; + + // Test direct navigation call with enabled and target + el.disabled = false; + el.target = "https://example.com"; + (el as unknown as { navigate: () => void }).navigate(); + expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; + + // Cleanup + windowOpenStub.restore(); + }); + + it("handles inline variant warning", async () => { + const consoleWarnSpy = sinon.spy(console, "warn"); + + // Test inline variant without text sibling + await fixture(html`
Link
`); + expect(consoleWarnSpy.calledOnce).to.be.true; + expect(consoleWarnSpy.calledWith( + "bl-link: Inline variant should be used within a text container. Example:

Text with a link inside.

" + )).to.be.true; + + // Test inline variant with text sibling + consoleWarnSpy.resetHistory(); + await fixture(html`
Text Link
`); + expect(consoleWarnSpy.notCalled).to.be.true; + + // Test with no parent element + consoleWarnSpy.resetHistory(); + const el = document.createElement("bl-link") as BlLink; + + el.variant = "inline"; + document.body.appendChild(el); + expect(consoleWarnSpy.calledOnce).to.be.true; + document.body.removeChild(el); + + // Test with parent element but no childNodes + consoleWarnSpy.resetHistory(); + const parentEl = document.createElement("div"); + + Object.defineProperty(parentEl, "childNodes", { value: null }); + const linkEl = document.createElement("bl-link") as BlLink; + + linkEl.variant = "inline"; + parentEl.appendChild(linkEl); + document.body.appendChild(parentEl); + expect(consoleWarnSpy.calledOnce).to.be.true; + document.body.removeChild(parentEl); + + // Test with non-inline variant + consoleWarnSpy.resetHistory(); + await fixture(html`Link`); + expect(consoleWarnSpy.notCalled).to.be.true; + + consoleWarnSpy.restore(); + }); + + it("handles all property combinations", async () => { + const el = await fixture(html` + Home + `); + + const link = el.shadowRoot!.querySelector("a"); + + expect(link?.classList.contains("link")).to.be.true; + expect(link?.classList.contains("standalone")).to.be.true; + expect(link?.classList.contains("size-large")).to.be.true; + expect(link?.classList.contains("kind-neutral")).to.be.true; + expect(link?.classList.contains("disabled")).to.be.true; + expect(link?.getAttribute("aria-label")).to.equal("Home page"); + expect(link?.getAttribute("target")).to.equal("_blank"); + expect(link?.getAttribute("rel")).to.equal("noopener noreferrer"); + expect(link?.getAttribute("href")).to.be.null; + expect(link?.getAttribute("tabindex")).to.equal("-1"); + expect(link?.getAttribute("aria-disabled")).to.equal("true"); + + // Test all size variants + const sizes = ["small", "medium", "large"] as const; + + for (const size of sizes) { + el.size = size; + await el.updateComplete; + expect(link?.classList.contains(`size-${size}`)).to.be.true; + } + + // Test all kind variants + const kinds = ["primary", "neutral"] as const; + + for (const kind of kinds) { + el.kind = kind; + await el.updateComplete; + expect(link?.classList.contains(`kind-${kind}`)).to.be.true; + } + }); +}); diff --git a/src/components/link/bl-link.ts b/src/components/link/bl-link.ts new file mode 100644 index 00000000..1f367745 --- /dev/null +++ b/src/components/link/bl-link.ts @@ -0,0 +1,166 @@ +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import { ifDefined } from "lit/directives/if-defined.js"; +import style from "./bl-link.css"; + +export type LinkVariant = "inline" | "standalone"; +export type LinkSize = "large" | "medium" | "small"; +export type LinkKind = "primary" | "neutral"; + +/** + * @tag bl-link + * @summary Baklava Link component for navigation + * + * @cssproperty [--bl-link-color=--bl-color-primary] Sets the color of link + * @cssproperty [--bl-link-hover-color=--bl-color-primary-hover] Sets the hover color of link + * @cssproperty [--bl-link-active-color=--bl-color-primary-active] Sets the active color of link + */ + +@customElement("bl-link") +export default class BlLink extends LitElement { + static get styles(): CSSResultGroup { + return [style]; + } + + /** + * Target URL for the link + */ + @property({ type: String, reflect: true }) + target = ""; + + /** + * Link variant - inline or standalone + */ + @property({ type: String, reflect: true }) + variant: LinkVariant = "inline"; + + /** + * Link size - only applies to standalone variant + */ + @property({ type: String, reflect: true }) + size: LinkSize = "medium"; + + /** + * Link kind - only applies to standalone variant + */ + @property({ type: String, reflect: true }) + kind: LinkKind = "primary"; + + /** + * Whether the link is external + */ + @property({ type: Boolean, reflect: true }) + external = false; + + /** + * Aria label for the link + */ + @property({ type: String, attribute: "aria-label" }) + ariaLabel = ""; + + /** + * Whether the link is disabled + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + + private get isStandalone(): boolean { + return this.variant === "standalone"; + } + + private renderIcon(): TemplateResult | null { + if (this.external) { + return html``; + } + if (this.isStandalone && !this.external) { + return html``; + } + return null; + } + + private handleKeyDown(event: KeyboardEvent) { + if (this.disabled) return; + + if (event.key === "Enter") { + event.preventDefault(); + this.navigate(); + } + } + + private handleClick(event: Event) { + if (this.disabled || !this.target) { + event.preventDefault(); + return; + } + + event.preventDefault(); + this.navigate(); + } + + private navigate() { + if (this.disabled || !this.target) return; + + window.open( + this.target, + this.external ? "_blank" : "_self", + this.external ? "noopener,noreferrer" : "" + ); + } + + connectedCallback() { + super.connectedCallback(); + + if (this.variant === "inline") { + const parentElement = this.parentElement; + const hasTextSibling = Array.from(parentElement?.childNodes || []).some( + node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() + ); + + if (!parentElement || !hasTextSibling) { + console.warn( + "bl-link: Inline variant should be used within a text container. Example:

Text with a link inside.

" + ); + } + } + } + + render(): TemplateResult { + const classes = { + link: true, + standalone: this.isStandalone, + [`size-${this.size}`]: this.isStandalone, + [`kind-${this.kind}`]: this.isStandalone, + disabled: this.disabled, + }; + + const content = html` + + ${this.renderIcon()} + `; + + return html` + + ${content} + ${this.external ? html`(opens in new tab)` : null} + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "bl-link": BlLink; + } +} diff --git a/src/components/link/docs/ADR/link-component-implementation.md b/src/components/link/docs/ADR/link-component-implementation.md new file mode 100644 index 00000000..fd5db840 --- /dev/null +++ b/src/components/link/docs/ADR/link-component-implementation.md @@ -0,0 +1,202 @@ +# Link Component Implementation + +## Status + +Implemented + +## Context + +We need a consistent way to handle navigation and links throughout the application. Links are fundamental UI elements that can be used in different contexts: + +- Within text content (inline) +- As standalone elements +- For internal navigation +- For external links + +## Decision + +We will implement a Link component with the following key characteristics: + +1. Two main variants: + - Inline Links: For use within text content (with validation) + - Standalone Links: For use as independent elements + +2. Design Constraints: + - Default color will be primary color from the color palette + - Standalone links will include an icon on the right + - Three sizes for standalone links: Large, Medium, and Small + - Links will support hover and focus states + - Links will support custom colors through CSS properties + - Links will have proper RTL support using logical properties + - Inline links must be used within text content + +3. Usage Guidelines: + - Links should only be used for navigation purposes + - For actions that change data or trigger functionality, buttons should be used instead + - Inline variant must be used within text content + - Links can target: + - Internal routes within the application + - External websites (with external icon) + - Elements on the same page (anchor links) + +4. Technical Implementation: + - TypeScript and Lit are used for type safety and web component implementation + - Props include: + ```typescript + interface LinkProps { + target: string; // Target URL for the link + variant?: "inline" | "standalone"; // Link variant (default: "inline") + size?: "large" | "medium" | "small"; // Link size for standalone variant (default: "medium") + kind?: "primary" | "neutral"; // Link kind for standalone variant (default: "primary") + external?: boolean; // Whether the link is external (default: false) + disabled?: boolean; // Whether the link appears disabled (default: false) + "aria-label"?: string; // Aria label for accessibility + } + ``` + - CSS Custom Properties: + ```css + :host { + --bl-link-color: var(--bl-color-primary); /* Default link color */ + --bl-link-hover-color: var(--bl-color-primary-hover); /* Hover state color */ + --bl-link-active-color: var(--bl-color-primary-active); /* Active state color */ + } + ``` + - Inline Variant Validation: + ```typescript + connectedCallback() { + super.connectedCallback(); + + if (this.variant === "inline") { + const parentElement = this.parentElement; + const hasTextSibling = Array.from(parentElement?.childNodes || []).some( + node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() + ); + + if (!parentElement || !hasTextSibling) { + console.warn( + "bl-link: Inline variant should be used within a text container. Example:

Text with a link inside.

" + ); + } + } + } + ``` + +5. Example Usage: + + 1. Basic Link: + ```html + About Page + ``` + + 2. Inline Link in Text (✅ Correct Usage): + ```html +

+ This is a paragraph with an + About Page + link in the text. +

+ ``` + + 3. Inline Link Without Text (⚠️ Warning): + ```html + +
+ About Page +
+ ``` + + 4. Standalone Link: + ```html + + About Page + + ``` + + 5. External Link: + ```html + + External Link + + ``` + + 6. Custom Colored Link: + ```html + + Success Link + + ``` + +## Features + +1. Variants: + - Inline: For use within text content (with validation) + - Standalone: For use as independent elements with icons + +2. Sizes (for standalone variant): + - Small + - Medium (default) + - Large + +3. Kinds (for standalone variant): + - Primary (default) + - Neutral + +4. States: + - Default + - Hover + - Active + - Focus + - Disabled + +5. Accessibility: + - Proper ARIA attributes + - Keyboard navigation support + - Focus management + - Screen reader support for external links + +6. RTL Support: + - Uses CSS logical properties + - Icons properly positioned in RTL layouts + +7. Validation: + - Inline variant usage validation + - Warning for incorrect usage + - Runtime checks for proper context + +## Consequences + +### Positive +- Consistent navigation pattern across the application +- Clear separation between navigation (links) and actions (buttons) +- Type-safe implementation with TypeScript and Lit +- Maintainable and scalable component structure +- Proper accessibility support +- RTL language support +- Customizable through CSS properties +- Validation for proper inline variant usage + +### Negative +- Additional complexity in maintaining two variants +- Need to educate team members on proper usage (links vs buttons) +- Additional development time for implementing all states and variants +- Runtime validation overhead for inline variant + +## Resources + +- [Storybook Documentation](https://baklava.design/components/link) +- [Figma Design](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=23617-1414) + From ab9fe45167290e17aa5f00dd1f3e117f9907ba81 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Wed, 25 Dec 2024 15:40:54 +0300 Subject: [PATCH 20/25] fix(link): set default font --- src/components/link/bl-link.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/link/bl-link.css b/src/components/link/bl-link.css index fe9c21e2..144b0328 100644 --- a/src/components/link/bl-link.css +++ b/src/components/link/bl-link.css @@ -8,6 +8,7 @@ --color: var(--bl-link-color, var(--bl-color-primary)); --hover-color: var(--bl-link-hover-color, var(--bl-color-primary-highlight)); --active-color: var(--bl-link-active-color, var(--bl-color-primary-highlight)); + --font: var(--bl-font-title-3-regular); /* Base styles */ display: inline-flex; @@ -16,6 +17,7 @@ cursor: pointer; outline: none; position: relative; + font: var(--font); } /* States */ From c94e9a1c7802f886649462d848d2da9ec80f66be Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Wed, 25 Dec 2024 15:54:56 +0300 Subject: [PATCH 21/25] fix(link): change default font family --- src/components/link/bl-link.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/link/bl-link.css b/src/components/link/bl-link.css index 144b0328..998a55b3 100644 --- a/src/components/link/bl-link.css +++ b/src/components/link/bl-link.css @@ -8,7 +8,6 @@ --color: var(--bl-link-color, var(--bl-color-primary)); --hover-color: var(--bl-link-hover-color, var(--bl-color-primary-highlight)); --active-color: var(--bl-link-active-color, var(--bl-color-primary-highlight)); - --font: var(--bl-font-title-3-regular); /* Base styles */ display: inline-flex; @@ -17,7 +16,7 @@ cursor: pointer; outline: none; position: relative; - font: var(--font); + font-family: var(--bl-font-family); } /* States */ From 412f661fc6f4702b3f58c51fdad92bcfe5fa3af3 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Wed, 25 Dec 2024 15:56:10 +0300 Subject: [PATCH 22/25] docs(link): enhance examples --- src/components/link/bl-link.stories.ts | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/components/link/bl-link.stories.ts b/src/components/link/bl-link.stories.ts index a4e5c9a0..30aaea58 100644 --- a/src/components/link/bl-link.stories.ts +++ b/src/components/link/bl-link.stories.ts @@ -135,7 +135,7 @@ const LinkTemplate = (args: LinkArgs) => html` export const Default: Story = { args: { target: "/", - content: "About Page", + content: "Link", }, render: LinkTemplate, }; @@ -144,7 +144,7 @@ export const InlineLink: Story = { args: { target: "/", variant: "inline", - content: "About Page", + content: "Link", }, render: (args) => html`
@@ -160,15 +160,15 @@ export const InlineLink: Story = {

- This is a paragraph with an ${LinkTemplate(args)} in the text. + This is a paragraph with a ${LinkTemplate(args)} in the text.

- This is a paragraph with an ${LinkTemplate(args)} in the text. + This is a paragraph with a ${LinkTemplate(args)} in the text.

- This is a paragraph with an ${LinkTemplate(args)} in the text. + This is a paragraph with a ${LinkTemplate(args)} in the text.

`, @@ -178,7 +178,7 @@ export const StandaloneLink: Story = { args: { target: "/", variant: "standalone", - content: "About Page", + content: "Link", }, render: LinkTemplate, }; @@ -215,12 +215,6 @@ export const ExternalLinks: Story = { render: () => html`
${LinkTemplate({ target: "https://example.com", external: true, content: "External Link" })} - ${LinkTemplate({ - target: "https://example.com", - variant: "standalone", - external: true, - content: "External Standalone", - })}
`, }; @@ -232,12 +226,6 @@ export const DisabledLinks: Story = { render: () => html`
${LinkTemplate({ target: "/", disabled: true, content: "Disabled Link" })} - ${LinkTemplate({ - target: "/", - variant: "standalone", - disabled: true, - content: "Disabled Standalone", - })}
`, }; From a6a08cc89a75920f1393370ec1df377e53c9c3b0 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Wed, 25 Dec 2024 16:07:34 +0300 Subject: [PATCH 23/25] fix(link): remove unnecessary navigation handling --- src/components/link/bl-link.test.ts | 114 ---------------------------- src/components/link/bl-link.ts | 31 -------- 2 files changed, 145 deletions(-) diff --git a/src/components/link/bl-link.test.ts b/src/components/link/bl-link.test.ts index f3643064..ac1e6cc3 100644 --- a/src/components/link/bl-link.test.ts +++ b/src/components/link/bl-link.test.ts @@ -61,120 +61,6 @@ describe("bl-link", () => { expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("external_link"); }); - it("handles navigation and events correctly", async () => { - const el = await fixture(html`Home`); - const windowOpenStub = sinon.stub(window, "open"); - - // Test internal navigation - const internalClickEvent = new MouseEvent("click", { bubbles: true }); - const internalClickSpy = sinon.spy(internalClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(internalClickEvent); - expect(internalClickSpy.called).to.be.true; - expect(windowOpenStub.calledWith("javascript:void(0)", "_self", "")).to.be.true; - - // Test external navigation - windowOpenStub.resetHistory(); - el.external = true; - el.target = "https://example.com"; - await el.updateComplete; - const externalClickEvent = new MouseEvent("click", { bubbles: true }); - const externalClickSpy = sinon.spy(externalClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(externalClickEvent); - expect(externalClickSpy.called).to.be.true; - expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; - - // Test disabled state - windowOpenStub.resetHistory(); - el.disabled = true; - await el.updateComplete; - const disabledClickEvent = new MouseEvent("click", { bubbles: true }); - const disabledClickSpy = sinon.spy(disabledClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledClickEvent); - expect(disabledClickSpy.called).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test keyboard navigation - el.disabled = false; - await el.updateComplete; - const enterKeyEvent = new KeyboardEvent("keydown", { key: "Enter", bubbles: true }); - const enterKeySpy = sinon.spy(enterKeyEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(enterKeyEvent); - expect(enterKeySpy.called).to.be.true; - expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; - - // Test keyboard navigation when disabled - windowOpenStub.resetHistory(); - el.disabled = true; - await el.updateComplete; - const disabledEnterKeyEvent = new KeyboardEvent("keydown", { key: "Enter", bubbles: true }); - const disabledEnterKeySpy = sinon.spy(disabledEnterKeyEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledEnterKeyEvent); - expect(disabledEnterKeySpy.notCalled).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test keyboard navigation with non-Enter key - el.disabled = false; - await el.updateComplete; - const spaceKeyEvent = new KeyboardEvent("keydown", { key: "Space", bubbles: true }); - const spaceKeySpy = sinon.spy(spaceKeyEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(spaceKeyEvent); - expect(spaceKeySpy.notCalled).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test navigation without target - el.target = ""; - await el.updateComplete; - const noTargetClickEvent = new MouseEvent("click", { bubbles: true }); - const noTargetClickSpy = sinon.spy(noTargetClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(noTargetClickEvent); - expect(noTargetClickSpy.called).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test navigation with disabled and no target - el.disabled = true; - el.target = ""; - await el.updateComplete; - const disabledNoTargetClickEvent = new MouseEvent("click", { bubbles: true }); - const disabledNoTargetClickSpy = sinon.spy(disabledNoTargetClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledNoTargetClickEvent); - expect(disabledNoTargetClickSpy.called).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test navigation with disabled and target - el.disabled = true; - el.target = "https://example.com"; - await el.updateComplete; - const disabledWithTargetClickEvent = new MouseEvent("click", { bubbles: true }); - const disabledWithTargetClickSpy = sinon.spy(disabledWithTargetClickEvent, "preventDefault"); - - el.shadowRoot!.querySelector("a")?.dispatchEvent(disabledWithTargetClickEvent); - expect(disabledWithTargetClickSpy.called).to.be.true; - expect(windowOpenStub.notCalled).to.be.true; - - // Test direct navigation call with disabled and target - el.disabled = true; - el.target = "https://example.com"; - (el as unknown as { navigate: () => void }).navigate(); - expect(windowOpenStub.notCalled).to.be.true; - - // Test direct navigation call with enabled and target - el.disabled = false; - el.target = "https://example.com"; - (el as unknown as { navigate: () => void }).navigate(); - expect(windowOpenStub.calledWith("https://example.com", "_blank", "noopener,noreferrer")).to.be.true; - - // Cleanup - windowOpenStub.restore(); - }); - it("handles inline variant warning", async () => { const consoleWarnSpy = sinon.spy(console, "warn"); diff --git a/src/components/link/bl-link.ts b/src/components/link/bl-link.ts index 1f367745..d6dbe907 100644 --- a/src/components/link/bl-link.ts +++ b/src/components/link/bl-link.ts @@ -79,35 +79,6 @@ export default class BlLink extends LitElement { return null; } - private handleKeyDown(event: KeyboardEvent) { - if (this.disabled) return; - - if (event.key === "Enter") { - event.preventDefault(); - this.navigate(); - } - } - - private handleClick(event: Event) { - if (this.disabled || !this.target) { - event.preventDefault(); - return; - } - - event.preventDefault(); - this.navigate(); - } - - private navigate() { - if (this.disabled || !this.target) return; - - window.open( - this.target, - this.external ? "_blank" : "_self", - this.external ? "noopener,noreferrer" : "" - ); - } - connectedCallback() { super.connectedCallback(); @@ -149,8 +120,6 @@ export default class BlLink extends LitElement { aria-label="${ifDefined(this.ariaLabel || undefined)}" aria-disabled="${this.disabled}" tabindex="${this.disabled ? "-1" : "0"}" - @click="${this.handleClick}" - @keydown="${this.handleKeyDown}" > ${content} ${this.external ? html`(opens in new tab)` : null} From e5dc48ea19c93212190a62993d6207a4bac986f5 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Thu, 26 Dec 2024 18:41:42 +0300 Subject: [PATCH 24/25] feat(link): add all anchor attrs, non-standalone links support slotted icons --- src/components/link/bl-link.css | 37 +--- src/components/link/bl-link.stories.ts | 169 ++++++++++++------ src/components/link/bl-link.test.ts | 82 +++++---- src/components/link/bl-link.ts | 79 +++++--- .../docs/ADR/link-component-implementation.md | 101 +++++++---- 5 files changed, 304 insertions(+), 164 deletions(-) diff --git a/src/components/link/bl-link.css b/src/components/link/bl-link.css index 998a55b3..91a772b6 100644 --- a/src/components/link/bl-link.css +++ b/src/components/link/bl-link.css @@ -22,13 +22,10 @@ /* States */ .link:hover { text-decoration: none; -} - -.link:hover:not(.disabled) { color: var(--hover-color); } -.link:active:not(.disabled) { +.link:active { color: var(--active-color); } @@ -50,8 +47,8 @@ color: var(--bl-color-primary); } -.link.standalone.kind-primary:hover:not(.disabled), -.link.standalone.kind-primary:active:not(.disabled) { +.link.standalone.kind-primary:hover, +.link.standalone.kind-primary:active { color: var(--bl-color-primary-highlight); } @@ -60,8 +57,8 @@ color: var(--bl-color-neutral); } -.link.standalone.kind-neutral:hover:not(.disabled), -.link.standalone.kind-neutral:active:not(.disabled) { +.link.standalone.kind-neutral:hover, +.link.standalone.kind-neutral:active { color: var(--bl-color-neutral-highlight); } @@ -82,25 +79,9 @@ } /* Icon styles */ -.icon { +::slotted([slot="icon"]) { margin-inline-start: var(--bl-size-3xs); -} - -/* Disabled state */ -.link.disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* Screen reader only text */ -.visually-hidden { - position: absolute; - block-size: 1px; - inline-size: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; + margin-inline-end: var(--bl-size-3xs); + display: inline-block; + vertical-align: middle; } diff --git a/src/components/link/bl-link.stories.ts b/src/components/link/bl-link.stories.ts index 30aaea58..175467a7 100644 --- a/src/components/link/bl-link.stories.ts +++ b/src/components/link/bl-link.stories.ts @@ -5,15 +5,21 @@ import { centeredLayout } from "../../utilities/chromatic-decorators"; interface LinkArgs { - target?: string; + href?: string; variant?: "inline" | "standalone"; size?: "small" | "medium" | "large"; kind?: "primary" | "neutral"; - external?: boolean; - disabled?: boolean; + target?: HTMLAnchorElement["target"]; + rel?: HTMLAnchorElement["rel"]; + hreflang?: HTMLAnchorElement["hreflang"]; + type?: HTMLAnchorElement["type"]; + referrerPolicy?: HTMLAnchorElement["referrerPolicy"]; + download?: HTMLAnchorElement["download"]; + ping?: HTMLAnchorElement["ping"]; "aria-label"?: string; content?: string; customStyles?: string; + icon?: string; } const FIGMA_LINK = "https://www.figma.com/design/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=23617-1414"; @@ -34,15 +40,17 @@ const meta: Meta = { component: "
" + "

" + - "The Link component is used for navigation between pages or to external URLs." + + "The Link component is used for navigation between pages or to external URLs. " + + "It supports all native anchor tag attributes and provides additional styling variants. " + + "When not using the standalone variant, you can provide a custom icon using the icon slot." + "

" + "
" + "" + - "ADR" + + "ADR" + "" + "" + - "Figma" + + "Figma" + "" + "
" + "
", @@ -53,9 +61,9 @@ const meta: Meta = { centeredLayout, ], argTypes: { - target: { + href: { control: "text", - description: "Target URL for the link", + description: "URL that the hyperlink points to", table: { type: { summary: "string" }, }, @@ -87,20 +95,65 @@ const meta: Meta = { defaultValue: { summary: "primary" }, }, }, - external: { - control: "boolean", - description: "Whether the link is external", + target: { + control: { type: "select" }, + options: ["_self", "_blank", "_parent", "_top"], + description: "Where to display the linked URL", + table: { + type: { summary: "HTMLAnchorElement['target']" }, + defaultValue: { summary: "_self" }, + }, + }, + rel: { + control: "text", + description: "Relationship between documents (e.g., noopener noreferrer)", + table: { + type: { summary: "HTMLAnchorElement['rel']" }, + }, + }, + hreflang: { + control: "text", + description: "Language of the linked document", + table: { + type: { summary: "HTMLAnchorElement['hreflang']" }, + }, + }, + type: { + control: "text", + description: "MIME type of the linked document", + table: { + type: { summary: "HTMLAnchorElement['type']" }, + }, + }, + referrerPolicy: { + control: { type: "select" }, + options: [ + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin", + "unsafe-url", + ], + description: "Referrer policy for the link", + table: { + type: { summary: "HTMLAnchorElement['referrerPolicy']" }, + }, + }, + download: { + control: "text", + description: "Whether to download the resource instead of navigating to it", table: { - type: { summary: "boolean" }, - defaultValue: { summary: false }, + type: { summary: "HTMLAnchorElement['download']" }, }, }, - disabled: { - control: "boolean", - description: "Whether the link appears disabled (changes cursor to not-allowed)", + ping: { + control: "text", + description: "URLs to be notified when following the link", table: { - type: { summary: "boolean" }, - defaultValue: { summary: false }, + type: { summary: "HTMLAnchorElement['ping']" }, }, }, "aria-label": { @@ -110,6 +163,13 @@ const meta: Meta = { type: { summary: "string" }, }, }, + icon: { + control: "text", + description: "Icon name for custom icon (only applies to non-standalone variants)", + table: { + type: { summary: "string" }, + }, + }, }, }; @@ -119,22 +179,28 @@ type Story = StoryObj; const LinkTemplate = (args: LinkArgs) => html` ${args.content || "Link Text"} + ${args.icon ? html`` : ""} `; export const Default: Story = { args: { - target: "/", + href: "/", content: "Link", }, render: LinkTemplate, @@ -142,7 +208,7 @@ export const Default: Story = { export const InlineLink: Story = { args: { - target: "/", + href: "/", variant: "inline", content: "Link", }, @@ -176,65 +242,68 @@ export const InlineLink: Story = { export const StandaloneLink: Story = { args: { - target: "/", + href: "/", variant: "standalone", content: "Link", }, render: LinkTemplate, }; -export const SizeVariants: Story = { +export const CustomIconLink: Story = { args: { - variant: "standalone", + href: "/", + content: "Link with Custom Icon", + icon: "external_link", }, - render: () => html` -
- ${LinkTemplate({ target: "/", variant: "standalone", size: "small", content: "Small" })} - ${LinkTemplate({ target: "/", variant: "standalone", size: "medium", content: "Medium" })} - ${LinkTemplate({ target: "/", variant: "standalone", size: "large", content: "Large" })} -
- `, + render: LinkTemplate, }; -export const KindVariants: Story = { +export const SizeVariants: Story = { args: { variant: "standalone", }, render: () => html`
- ${LinkTemplate({ target: "/", variant: "standalone", kind: "primary", content: "Primary" })} - ${LinkTemplate({ target: "/", variant: "standalone", kind: "neutral", content: "Neutral" })} + ${LinkTemplate({ href: "/", variant: "standalone", size: "small", content: "Small" })} + ${LinkTemplate({ href: "/", variant: "standalone", size: "medium", content: "Medium" })} + ${LinkTemplate({ href: "/", variant: "standalone", size: "large", content: "Large" })}
`, }; -export const ExternalLinks: Story = { +export const KindVariants: Story = { args: { - external: true, + variant: "standalone", }, render: () => html`
- ${LinkTemplate({ target: "https://example.com", external: true, content: "External Link" })} + ${LinkTemplate({ href: "/", variant: "standalone", kind: "primary", content: "Primary" })} + ${LinkTemplate({ href: "/", variant: "standalone", kind: "neutral", content: "Neutral" })}
`, }; -export const DisabledLinks: Story = { +export const NativeAnchorAttributes: Story = { args: { - disabled: true, + href: "https://example.com", + target: "_blank", + rel: "noopener noreferrer", + hreflang: "en", + type: "text/html", + referrerPolicy: "no-referrer", + download: "file.pdf", + ping: "https://analytics.example.com", + content: "External Link with Native Attributes", }, - render: () => html` -
- ${LinkTemplate({ target: "/", disabled: true, content: "Disabled Link" })} -
- `, + render: LinkTemplate, }; export const AccessibleLink: Story = { args: { - target: "/", + href: "/", "aria-label": "View your profile settings", content: "Profile", + }, render: LinkTemplate, }; @@ -243,17 +312,17 @@ export const CustomInlineColors: Story = { render: () => html`
${LinkTemplate({ - target: "/", + href: "/", content: "Success Link", customStyles: "--bl-link-color: var(--bl-color-success); --bl-link-hover-color: var(--bl-color-success-highlight); --bl-link-active-color: var(--bl-color-success-highlight);" })} ${LinkTemplate({ - target: "/", + href: "/", content: "Warning Link", customStyles: "--bl-link-color: var(--bl-color-warning); --bl-link-hover-color: var(--bl-color-warning-highlight); --bl-link-active-color: var(--bl-color-warning-highlight);" })} ${LinkTemplate({ - target: "/", + href: "/", content: "Danger Link", customStyles: "--bl-link-color: var(--bl-color-danger); --bl-link-hover-color: var(--bl-color-danger-highlight); --bl-link-active-color: var(--bl-color-danger-highlight);" })} diff --git a/src/components/link/bl-link.test.ts b/src/components/link/bl-link.test.ts index ac1e6cc3..010a4f8d 100644 --- a/src/components/link/bl-link.test.ts +++ b/src/components/link/bl-link.test.ts @@ -8,57 +8,61 @@ describe("bl-link", () => { }); it("renders with default properties", async () => { - const el = await fixture(html`Home`); + const el = await fixture(html`Home`); expect(el.variant).to.equal("inline"); expect(el.size).to.equal("medium"); expect(el.kind).to.equal("primary"); - expect(el.external).to.be.false; - expect(el.disabled).to.be.false; - expect(el.target).to.equal("javascript:void(0)"); + expect(el.href).to.equal("javascript:void(0)"); + expect(el.target).to.equal("_self"); + expect(el.rel).to.equal(""); + expect(el.hreflang).to.equal(""); + expect(el.type).to.equal(""); + expect(el.download).to.equal(""); + expect(el.ping).to.equal(""); expect(el.ariaLabel).to.equal(""); }); it("renders link attributes correctly", async () => { - const el = await fixture(html`Home`); + const el = await fixture(html` + External Link + `); const link = el.shadowRoot!.querySelector("a"); expect(link).to.exist; - expect(link?.getAttribute("href")).to.equal("javascript:void(0)"); - expect(link?.getAttribute("target")).to.equal("_self"); - expect(link?.getAttribute("rel")).to.be.null; + expect(link?.getAttribute("href")).to.equal("https://example.com"); + expect(link?.getAttribute("target")).to.equal("_blank"); + expect(link?.getAttribute("rel")).to.equal("noopener"); + expect(link?.getAttribute("hreflang")).to.equal("en"); + expect(link?.getAttribute("type")).to.equal("text/html"); + expect(link?.getAttribute("referrerpolicy")).to.equal("no-referrer"); + expect(link?.getAttribute("download")).to.equal("file.pdf"); + expect(link?.getAttribute("ping")).to.equal("https://analytics.example.com"); expect(link?.getAttribute("role")).to.equal("link"); - expect(link?.getAttribute("aria-disabled")).to.equal("false"); expect(link?.getAttribute("tabindex")).to.equal("0"); - expect(link?.getAttribute("aria-label")).to.be.null; }); it("renders icons correctly", async () => { - const el = await fixture(html`Home`); + const el = await fixture(html`Home`); expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("arrow_right"); - el.external = true; - await el.updateComplete; - expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("external_link"); - expect(el.shadowRoot!.querySelector(".visually-hidden")?.textContent).to.equal("(opens in new tab)"); - el.variant = "inline"; - el.external = false; await el.updateComplete; expect(el.shadowRoot!.querySelector("bl-icon")).to.be.null; - // Test standalone without external el.variant = "standalone"; - el.external = false; await el.updateComplete; expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("arrow_right"); - - // Test inline with external - el.variant = "inline"; - el.external = true; - await el.updateComplete; - expect(el.shadowRoot!.querySelector("bl-icon")?.getAttribute("name")).to.equal("external_link"); }); it("handles inline variant warning", async () => { @@ -109,13 +113,16 @@ describe("bl-link", () => { it("handles all property combinations", async () => { const el = await fixture(html` Home `); @@ -125,13 +132,13 @@ describe("bl-link", () => { expect(link?.classList.contains("standalone")).to.be.true; expect(link?.classList.contains("size-large")).to.be.true; expect(link?.classList.contains("kind-neutral")).to.be.true; - expect(link?.classList.contains("disabled")).to.be.true; expect(link?.getAttribute("aria-label")).to.equal("Home page"); + expect(link?.getAttribute("href")).to.equal("javascript:void(0)"); expect(link?.getAttribute("target")).to.equal("_blank"); - expect(link?.getAttribute("rel")).to.equal("noopener noreferrer"); - expect(link?.getAttribute("href")).to.be.null; - expect(link?.getAttribute("tabindex")).to.equal("-1"); - expect(link?.getAttribute("aria-disabled")).to.equal("true"); + expect(link?.getAttribute("rel")).to.equal("noopener"); + expect(link?.getAttribute("hreflang")).to.equal("en"); + expect(link?.getAttribute("type")).to.equal("text/html"); + expect(link?.getAttribute("referrerpolicy")).to.equal("no-referrer"); // Test all size variants const sizes = ["small", "medium", "large"] as const; @@ -150,5 +157,14 @@ describe("bl-link", () => { await el.updateComplete; expect(link?.classList.contains(`kind-${kind}`)).to.be.true; } + + // Test all target variants + const targets: ["_self", "_blank", "_parent", "_top"] = ["_self", "_blank", "_parent", "_top"]; + + for (const target of targets) { + el.target = target; + await el.updateComplete; + expect(link?.getAttribute("target")).to.equal(target); + } }); }); diff --git a/src/components/link/bl-link.ts b/src/components/link/bl-link.ts index d6dbe907..aad099f3 100644 --- a/src/components/link/bl-link.ts +++ b/src/components/link/bl-link.ts @@ -12,6 +12,8 @@ export type LinkKind = "primary" | "neutral"; * @tag bl-link * @summary Baklava Link component for navigation * + * @slot icon - Custom icon slot for non-standalone variants + * * @cssproperty [--bl-link-color=--bl-color-primary] Sets the color of link * @cssproperty [--bl-link-hover-color=--bl-color-primary-hover] Sets the hover color of link * @cssproperty [--bl-link-active-color=--bl-color-primary-active] Sets the active color of link @@ -24,10 +26,10 @@ export default class BlLink extends LitElement { } /** - * Target URL for the link + * URL that the hyperlink points to */ @property({ type: String, reflect: true }) - target = ""; + href: HTMLAnchorElement["href"] = ""; /** * Link variant - inline or standalone @@ -47,12 +49,6 @@ export default class BlLink extends LitElement { @property({ type: String, reflect: true }) kind: LinkKind = "primary"; - /** - * Whether the link is external - */ - @property({ type: Boolean, reflect: true }) - external = false; - /** * Aria label for the link */ @@ -60,23 +56,58 @@ export default class BlLink extends LitElement { ariaLabel = ""; /** - * Whether the link is disabled + * Where to display the linked URL */ - @property({ type: Boolean, reflect: true }) - disabled = false; + @property({ type: String, reflect: true }) + target: HTMLAnchorElement["target"] = "_self"; + + /** + * Relationship between the current document and the linked document. + * Multiple rel values can be specified by separating them with spaces. + * Example: "noopener noreferrer" + */ + @property({ type: String, reflect: true }) + rel: HTMLAnchorElement["rel"] = ""; + + /** + * Language of the linked document + */ + @property({ type: String, reflect: true }) + hreflang: HTMLAnchorElement["hreflang"] = ""; + + /** + * MIME type of the linked document + */ + @property({ type: String, reflect: true }) + type: HTMLAnchorElement["type"] = ""; + + /** + * Referrer policy for the link + */ + @property({ type: String, reflect: true, attribute: "referrerpolicy" }) + referrerPolicy: HTMLAnchorElement["referrerPolicy"] = ""; + + /** + * Whether to download the resource instead of navigating to it + */ + @property({ type: String, reflect: true }) + download: HTMLAnchorElement["download"] = ""; + + /** + * Ping URLs to be notified when following the link + */ + @property({ type: String, reflect: true }) + ping: HTMLAnchorElement["ping"] = ""; private get isStandalone(): boolean { return this.variant === "standalone"; } private renderIcon(): TemplateResult | null { - if (this.external) { - return html``; - } - if (this.isStandalone && !this.external) { + if (this.isStandalone) { return html``; } - return null; + return html``; } connectedCallback() { @@ -102,7 +133,6 @@ export default class BlLink extends LitElement { standalone: this.isStandalone, [`size-${this.size}`]: this.isStandalone, [`kind-${this.kind}`]: this.isStandalone, - disabled: this.disabled, }; const content = html` @@ -112,17 +142,20 @@ export default class BlLink extends LitElement { return html` ${content} - ${this.external ? html`(opens in new tab)` : null} `; } diff --git a/src/components/link/docs/ADR/link-component-implementation.md b/src/components/link/docs/ADR/link-component-implementation.md index fd5db840..ada934bd 100644 --- a/src/components/link/docs/ADR/link-component-implementation.md +++ b/src/components/link/docs/ADR/link-component-implementation.md @@ -11,7 +11,6 @@ We need a consistent way to handle navigation and links throughout the applicati - Within text content (inline) - As standalone elements - For internal navigation -- For external links ## Decision @@ -19,11 +18,12 @@ We will implement a Link component with the following key characteristics: 1. Two main variants: - Inline Links: For use within text content (with validation) - - Standalone Links: For use as independent elements + - Standalone Links: For use as independent elements with arrow icon 2. Design Constraints: - Default color will be primary color from the color palette - - Standalone links will include an icon on the right + - Standalone links will include an arrow icon on the right + - Non-standalone links can have custom icons via slot - Three sizes for standalone links: Large, Medium, and Small - Links will support hover and focus states - Links will support custom colors through CSS properties @@ -36,7 +36,6 @@ We will implement a Link component with the following key characteristics: - Inline variant must be used within text content - Links can target: - Internal routes within the application - - External websites (with external icon) - Elements on the same page (anchor links) 4. Technical Implementation: @@ -44,15 +43,26 @@ We will implement a Link component with the following key characteristics: - Props include: ```typescript interface LinkProps { - target: string; // Target URL for the link - variant?: "inline" | "standalone"; // Link variant (default: "inline") - size?: "large" | "medium" | "small"; // Link size for standalone variant (default: "medium") - kind?: "primary" | "neutral"; // Link kind for standalone variant (default: "primary") - external?: boolean; // Whether the link is external (default: false) - disabled?: boolean; // Whether the link appears disabled (default: false) - "aria-label"?: string; // Aria label for accessibility + href: HTMLAnchorElement["href"]; // URL that the hyperlink points to + variant?: "inline" | "standalone"; // Link variant (default: "inline") + size?: "large" | "medium" | "small"; // Link size for standalone variant (default: "medium") + kind?: "primary" | "neutral"; // Link kind for standalone variant (default: "primary") + target?: HTMLAnchorElement["target"]; // Where to display the linked URL (default: "_self") + rel?: HTMLAnchorElement["rel"]; // Relationship between documents + hreflang?: HTMLAnchorElement["hreflang"]; // Language of the linked document + type?: HTMLAnchorElement["type"]; // MIME type of the linked document + referrerPolicy?: HTMLAnchorElement["referrerPolicy"]; // Referrer policy for the link + download?: HTMLAnchorElement["download"]; // Whether to download the resource + ping?: HTMLAnchorElement["ping"]; // URLs to be notified when following the link + "aria-label"?: string; // Aria label for accessibility } ``` + - Slots: + ```typescript + /** + * @slot icon - Custom icon slot for non-standalone variants + */ + ``` - CSS Custom Properties: ```css :host { @@ -85,14 +95,14 @@ We will implement a Link component with the following key characteristics: 1. Basic Link: ```html - About Page + About Page ``` 2. Inline Link in Text (✅ Correct Usage): ```html

This is a paragraph with an - About Page + About Page link in the text.

``` @@ -101,14 +111,14 @@ We will implement a Link component with the following key characteristics: ```html
- About Page + About Page
``` 4. Standalone Link: ```html @@ -116,20 +126,35 @@ We will implement a Link component with the following key characteristics: ``` - 5. External Link: + 5. Link with Custom Icon: + ```html + + Settings + + + ``` + + 6. Link with Native Anchor Attributes: ```html External Link + ``` - 6. Custom Colored Link: + 7. Custom Colored Link: ```html Success Link + ``` @@ -144,35 +170,47 @@ We will implement a Link component with the following key characteristics: 1. Variants: - Inline: For use within text content (with validation) - - Standalone: For use as independent elements with icons + - Standalone: For use as independent elements with arrow icon + +2. Icons: + - Standalone: Fixed arrow icon on the right + - Non-standalone: Customizable icon via slot -2. Sizes (for standalone variant): +3. Sizes (for standalone variant): - Small - Medium (default) - Large -3. Kinds (for standalone variant): +4. Kinds (for standalone variant): - Primary (default) - Neutral -4. States: +5. States: - Default - Hover - Active - Focus - - Disabled -5. Accessibility: +6. Native Anchor Attributes: + - href: URL destination + - target: Link target (_self, _blank, etc.) + - rel: Document relationships + - hreflang: Language of linked document + - type: MIME type + - referrerPolicy: Referrer policy + - download: Download behavior + - ping: Ping notifications + +7. Accessibility: - Proper ARIA attributes - Keyboard navigation support - Focus management - - Screen reader support for external links -6. RTL Support: +8. RTL Support: - Uses CSS logical properties - Icons properly positioned in RTL layouts -7. Validation: +9. Validation: - Inline variant usage validation - Warning for incorrect usage - Runtime checks for proper context @@ -182,7 +220,9 @@ We will implement a Link component with the following key characteristics: ### Positive - Consistent navigation pattern across the application - Clear separation between navigation (links) and actions (buttons) -- Type-safe implementation with TypeScript and Lit +- Type-safe implementation with TypeScript and native HTML types +- Full support for all native anchor tag attributes +- Flexible icon customization for non-standalone variants - Maintainable and scalable component structure - Proper accessibility support - RTL language support @@ -199,4 +239,5 @@ We will implement a Link component with the following key characteristics: - [Storybook Documentation](https://baklava.design/components/link) - [Figma Design](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=23617-1414) +- [MDN Anchor Element Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) From d6f16743a4373eb346bc219def3157d03382d401 Mon Sep 17 00:00:00 2001 From: Erbil Nas Date: Thu, 26 Dec 2024 19:15:18 +0300 Subject: [PATCH 25/25] fix(link): make anchor attrs optional --- src/components/link/bl-link.test.ts | 24 +++++++++++++++++++----- src/components/link/bl-link.ts | 12 ++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/components/link/bl-link.test.ts b/src/components/link/bl-link.test.ts index 010a4f8d..813025e9 100644 --- a/src/components/link/bl-link.test.ts +++ b/src/components/link/bl-link.test.ts @@ -15,11 +15,12 @@ describe("bl-link", () => { expect(el.kind).to.equal("primary"); expect(el.href).to.equal("javascript:void(0)"); expect(el.target).to.equal("_self"); - expect(el.rel).to.equal(""); - expect(el.hreflang).to.equal(""); - expect(el.type).to.equal(""); - expect(el.download).to.equal(""); - expect(el.ping).to.equal(""); + expect(el.rel).to.be.undefined; + expect(el.hreflang).to.be.undefined; + expect(el.type).to.be.undefined; + expect(el.referrerPolicy).to.be.undefined; + expect(el.download).to.be.undefined; + expect(el.ping).to.be.undefined; expect(el.ariaLabel).to.equal(""); }); @@ -167,4 +168,17 @@ describe("bl-link", () => { expect(link?.getAttribute("target")).to.equal(target); } }); + + it("renders custom icon slot", async () => { + const el = await fixture(html` + + Link Text + + + `); + + const slot = el.shadowRoot!.querySelector('slot[name="icon"]'); + + expect(slot).to.exist; + }); }); diff --git a/src/components/link/bl-link.ts b/src/components/link/bl-link.ts index aad099f3..3cd132d3 100644 --- a/src/components/link/bl-link.ts +++ b/src/components/link/bl-link.ts @@ -67,37 +67,37 @@ export default class BlLink extends LitElement { * Example: "noopener noreferrer" */ @property({ type: String, reflect: true }) - rel: HTMLAnchorElement["rel"] = ""; + rel?: HTMLAnchorElement["rel"]; /** * Language of the linked document */ @property({ type: String, reflect: true }) - hreflang: HTMLAnchorElement["hreflang"] = ""; + hreflang?: HTMLAnchorElement["hreflang"]; /** * MIME type of the linked document */ @property({ type: String, reflect: true }) - type: HTMLAnchorElement["type"] = ""; + type?: HTMLAnchorElement["type"]; /** * Referrer policy for the link */ @property({ type: String, reflect: true, attribute: "referrerpolicy" }) - referrerPolicy: HTMLAnchorElement["referrerPolicy"] = ""; + referrerPolicy?: HTMLAnchorElement["referrerPolicy"]; /** * Whether to download the resource instead of navigating to it */ @property({ type: String, reflect: true }) - download: HTMLAnchorElement["download"] = ""; + download?: HTMLAnchorElement["download"]; /** * Ping URLs to be notified when following the link */ @property({ type: String, reflect: true }) - ping: HTMLAnchorElement["ping"] = ""; + ping?: HTMLAnchorElement["ping"]; private get isStandalone(): boolean { return this.variant === "standalone";