Compare commits
22 Commits
v0.1.11
...
renovate/c
| Author | SHA1 | Date | |
|---|---|---|---|
| 9056f3fc10 | |||
| 1523f7be2c | |||
| b664c99944 | |||
| 3d4a4a5f57 | |||
| f9864842b5 | |||
| dd084369e9 | |||
| 850eed0766 | |||
| 4904bea29c | |||
| 4fc3738adf | |||
| e17c82de2f | |||
| a527ce27cd | |||
| 623e45d241 | |||
| 5593746cf4 | |||
| b163fdaa62 | |||
| ec63a10027 | |||
| 5ada69773c | |||
| 370d6e7e0a | |||
| 44dd5d5deb | |||
| 8d3ca5a281 | |||
| 1d5113d209 | |||
| f71e773a3a | |||
| 4921afe296 |
51
.drone.yml
51
.drone.yml
@@ -13,48 +13,65 @@ trigger:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: install
|
- name: install
|
||||||
image: node:22
|
image: node:25
|
||||||
commands:
|
commands:
|
||||||
- corepack enable
|
|
||||||
- corepack prepare yarn@1.22.22 --activate
|
|
||||||
- yarn install --frozen-lockfile
|
- yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: lint
|
- name: lint
|
||||||
image: node:22
|
image: node:25
|
||||||
commands:
|
commands:
|
||||||
- corepack enable
|
|
||||||
- corepack prepare yarn@1.22.22 --activate
|
|
||||||
- yarn lint
|
- yarn lint
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: node:22
|
image: node:25
|
||||||
commands:
|
commands:
|
||||||
- corepack enable
|
|
||||||
- corepack prepare yarn@1.22.22 --activate
|
|
||||||
- yarn build
|
- yarn build
|
||||||
|
|
||||||
|
- name: unit-tests
|
||||||
|
image: node:25
|
||||||
|
environment:
|
||||||
|
NODE_OPTIONS: --no-webstorage
|
||||||
|
commands:
|
||||||
|
- yarn test:coverage
|
||||||
|
- test -f coverage/lcov.info
|
||||||
|
|
||||||
|
- name: code-analysis
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
image: sonarsource/sonar-scanner-cli:latest
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
test -f coverage/lcov.info
|
||||||
|
SONAR_ARGS="-Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.host.url=$SONAR_INSTANCE_URL -Dsonar.token=$SONAR_LOGIN_KEY -Dsonar.sources=src -Dsonar.tests=tests -Dsonar.test.inclusions=tests/**/*.{test,spec}.{ts,tsx,js,jsx} -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info -Dsonar.working.directory=/tmp/.scannerwork"
|
||||||
|
sonar-scanner $SONAR_ARGS
|
||||||
|
environment:
|
||||||
|
SONAR_USER_HOME: /tmp/.sonar
|
||||||
|
SONAR_PROJECT_KEY:
|
||||||
|
from_secret: sonar_project_key
|
||||||
|
SONAR_INSTANCE_URL:
|
||||||
|
from_secret: sonar_instance_url
|
||||||
|
SONAR_LOGIN_KEY:
|
||||||
|
from_secret: sonar_login_key
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: web-ui-publish
|
name: web-ui-publish
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
event:
|
||||||
- promote
|
- tag
|
||||||
target:
|
ref:
|
||||||
- production
|
- refs/tags/v*
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: publish-npm
|
- name: publish-npm
|
||||||
image: node:22
|
image: node:25
|
||||||
environment:
|
environment:
|
||||||
NEXUS_NPM_TOKEN:
|
NEXUS_NPM_TOKEN:
|
||||||
from_secret: nexus_npm_token
|
from_secret: nexus_npm_token
|
||||||
commands:
|
commands:
|
||||||
- corepack enable
|
|
||||||
- corepack prepare yarn@1.22.22 --activate
|
|
||||||
- yarn install --frozen-lockfile
|
- yarn install --frozen-lockfile
|
||||||
- npm config set //nexus.beatrice.wtf/repository/npm-hosted/:_authToken "$NEXUS_NPM_TOKEN"
|
- npm config set //nexus.beatrice.wtf/repository/npm-hosted/:_authToken "$NEXUS_NPM_TOKEN"
|
||||||
- yarn publish:nexus
|
- yarn publish:nexus
|
||||||
|
|||||||
@@ -45,4 +45,14 @@ export default tseslint.config(
|
|||||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['tests/**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
...globals.vitest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@panic/web-ui",
|
"name": "@panic/web-ui",
|
||||||
"version": "0.1.11",
|
"version": "0.1.18",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"description": "Core components for panic.haus web applications",
|
"description": "Core components for panic.haus web applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"build": "yarn clean && vite build && tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/base.css dist/styles/base.css && tailwindcss -c tailwind.build.config.cjs -i src/styles/components.css -o dist/styles/components.css --minify && tailwindcss -c tailwind.build.config.cjs -i src/styles/utilities.css -o dist/styles/utilities.css --minify && cp tailwind-preset.cjs dist/tailwind-preset.cjs",
|
"build": "yarn clean && vite build && tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/base.css dist/styles/base.css && tailwindcss -c tailwind.build.config.cjs -i src/styles/components.css -o dist/styles/components.css --minify && tailwindcss -c tailwind.build.config.cjs -i src/styles/utilities.css -o dist/styles/utilities.css --minify && cp tailwind-preset.cjs dist/tailwind-preset.cjs",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:coverage": "vitest run --coverage --coverage.reporter=lcov --coverage.reporter=text-summary",
|
||||||
|
"test:watch": "vitest",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"format": "prettier . --write",
|
"format": "prettier . --write",
|
||||||
@@ -64,13 +67,17 @@
|
|||||||
"@storybook/react": "^10.2.10",
|
"@storybook/react": "^10.2.10",
|
||||||
"@storybook/react-vite": "^10.2.10",
|
"@storybook/react-vite": "^10.2.10",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"eslint": "^10",
|
"eslint": "^10",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.1.0-canary-ab18f33d-20260220",
|
||||||
"eslint-plugin-react-refresh": "^0.5.1",
|
"eslint-plugin-react-refresh": "^0.5.1",
|
||||||
"globals": "^17.3.0",
|
"globals": "^17.3.0",
|
||||||
|
"jsdom": "^28.1.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -81,6 +88,10 @@
|
|||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"typescript-eslint": "^8.56.0",
|
"typescript-eslint": "^8.56.0",
|
||||||
"vite": "^7.0.0",
|
"vite": "^7.0.0",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
"yjs": "^13.6.24"
|
"yjs": "^13.6.24"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^25.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const meta = {
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component:
|
||||||
'Date selection field with InputField-compatible API, supporting date/time/datetime-local values, size/layout variants, and validation state.',
|
'In-house date/time selection field with InputField-compatible API. Uses a custom popup (not native browser pickers) and supports date, time, and date-time modes.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -27,10 +27,10 @@ const meta = {
|
|||||||
table: { type: { summary: 'string' } },
|
table: { type: { summary: 'string' } },
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
description: 'Native date input type.',
|
description: 'DatePicker mode.',
|
||||||
options: ['date', 'datetime-local', 'time'],
|
options: ['date', 'date-time', 'time'],
|
||||||
control: 'inline-radio',
|
control: 'inline-radio',
|
||||||
table: { type: { summary: "'date' | 'datetime-local' | 'time'" } },
|
table: { type: { summary: "'date' | 'date-time' | 'time'" } },
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
description: 'Input size.',
|
description: 'Input size.',
|
||||||
@@ -55,6 +55,22 @@ const meta = {
|
|||||||
control: 'text',
|
control: 'text',
|
||||||
table: { type: { summary: 'string' } },
|
table: { type: { summary: 'string' } },
|
||||||
},
|
},
|
||||||
|
format: {
|
||||||
|
description:
|
||||||
|
'Optional input/output format. Supported tokens: `dd`, `mm`, `yyyy`, `HH` (for example `dd/mm/yyyy HH:mm`).',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
description: 'Optional minimum value in the same format as `value`.',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
description: 'Optional maximum value in the same format as `value`.',
|
||||||
|
control: 'text',
|
||||||
|
table: { type: { summary: 'string' } },
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
description: 'Native input `name` attribute.',
|
description: 'Native input `name` attribute.',
|
||||||
control: 'text',
|
control: 'text',
|
||||||
@@ -108,7 +124,7 @@ const meta = {
|
|||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
value: '',
|
value: '',
|
||||||
size: 'md',
|
size: 'md',
|
||||||
width: 'md',
|
width: 'md',
|
||||||
@@ -124,8 +140,8 @@ export const DateOnly: Story = {
|
|||||||
type: 'date',
|
type: 'date',
|
||||||
label: 'Publish date',
|
label: 'Publish date',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function DateOnlyRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20');
|
const [value, setValue] = useState('2031/05/20');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...args}
|
{...args}
|
||||||
@@ -141,11 +157,11 @@ export const DateOnly: Story = {
|
|||||||
|
|
||||||
export const DateTime: Story = {
|
export const DateTime: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function DateTimeRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20T14:30');
|
const [value, setValue] = useState('2031/05/20 14:30');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...args}
|
{...args}
|
||||||
@@ -167,7 +183,7 @@ export const TimeOnlyInline: Story = {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
|
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function TimeOnlyInlineRender(args) {
|
||||||
const [value, setValue] = useState('09:00');
|
const [value, setValue] = useState('09:00');
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@@ -182,9 +198,10 @@ export const TimeOnlyInline: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
value: '',
|
value: '',
|
||||||
error: 'Pick a valid future date and time',
|
error: 'Pick a valid future date and time',
|
||||||
@@ -193,20 +210,20 @@ export const Error: Story = {
|
|||||||
|
|
||||||
export const Disabled: Story = {
|
export const Disabled: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Published at',
|
label: 'Published at',
|
||||||
value: '2031-05-20T14:30',
|
value: '2031/05/20 14:30',
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SizeMatrix: Story = {
|
export const SizeMatrix: Story = {
|
||||||
args: {
|
args: {
|
||||||
type: 'datetime-local',
|
type: 'date-time',
|
||||||
label: 'Schedule at',
|
label: 'Schedule at',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState('2031-05-20T14:30');
|
const [value, setValue] = useState('2031/05/20 14:30');
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@@ -238,3 +255,26 @@ export const SizeMatrix: Story = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CustomFormatWithRange: Story = {
|
||||||
|
args: {
|
||||||
|
type: 'date-time',
|
||||||
|
label: 'Starts at',
|
||||||
|
format: 'dd/mm/yyyy HH:mm',
|
||||||
|
min: '10/03/2026 09:00',
|
||||||
|
max: '24/03/2026 18:30',
|
||||||
|
},
|
||||||
|
render: function CustomFormatWithRangeRender(args) {
|
||||||
|
const [value, setValue] = useState('22/03/2026 14:30');
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
{...args}
|
||||||
|
value={value}
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
args.onChange?.(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -99,7 +99,7 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Stacked: Story = {
|
export const Stacked: Story = {
|
||||||
render: (args) => {
|
render: function StackedRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -119,7 +119,7 @@ export const Inline: Story = {
|
|||||||
layout: 'inline',
|
layout: 'inline',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function InlineRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -147,7 +147,7 @@ export const WithError: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SizeMatrix: Story = {
|
export const SizeMatrix: Story = {
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState(args.value);
|
const [value, setValue] = useState(args.value);
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const Basic: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const WithActions: Story = {
|
export const WithActions: Story = {
|
||||||
render: (args) => {
|
render: function WithActionsRender(args) {
|
||||||
const [title, setTitle] = useState('Storybook powered CMS');
|
const [title, setTitle] = useState('Storybook powered CMS');
|
||||||
const [status, setStatus] = useState('draft');
|
const [status, setStatus] = useState('draft');
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const Text: Story = {
|
|||||||
label: 'Title',
|
label: 'Title',
|
||||||
placeholder: 'Write a title',
|
placeholder: 'Write a title',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function TextRender(args) {
|
||||||
const [value, setValue] = useState('Storybook integration');
|
const [value, setValue] = useState('Storybook integration');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -148,7 +148,7 @@ export const PasswordWithToggle: Story = {
|
|||||||
label: 'Password',
|
label: 'Password',
|
||||||
placeholder: 'Type a strong password',
|
placeholder: 'Type a strong password',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function PasswordWithToggleRender(args) {
|
||||||
const [value, setValue] = useState('pa55word');
|
const [value, setValue] = useState('pa55word');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -171,7 +171,7 @@ export const InlineWithIcon: Story = {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
|
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function InlineWithIconRender(args) {
|
||||||
const [value, setValue] = useState('posts');
|
const [value, setValue] = useState('posts');
|
||||||
return (
|
return (
|
||||||
<InputField
|
<InputField
|
||||||
@@ -186,7 +186,8 @@ export const InlineWithIcon: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
@@ -210,7 +211,7 @@ export const SizeMatrix: Story = {
|
|||||||
label: 'Name',
|
label: 'Name',
|
||||||
placeholder: 'Enter value',
|
placeholder: 'Enter value',
|
||||||
},
|
},
|
||||||
render: (args) => {
|
render: function SizeMatrixRender(args) {
|
||||||
const [value, setValue] = useState('Beatrice');
|
const [value, setValue] = useState('Beatrice');
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-3">
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Body: Story = {};
|
export const Body: Story = {};
|
||||||
|
|
||||||
export const Error: Story = {
|
export const ErrorState: Story = {
|
||||||
|
name: 'Error',
|
||||||
args: {
|
args: {
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
children: 'This field is required',
|
children: 'This field is required',
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof meta>;
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
export const Editable: Story = {
|
export const Editable: Story = {
|
||||||
render: (args) => {
|
render: function EditableRender(args) {
|
||||||
const [markdown, setMarkdown] = useState(args.markdown);
|
const [markdown, setMarkdown] = useState(args.markdown);
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-2xl">
|
<div className="w-full max-w-2xl">
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export const Empty: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const InteractiveSortingAndPagination: Story = {
|
export const InteractiveSortingAndPagination: Story = {
|
||||||
render: () => {
|
render: function InteractiveSortingAndPaginationRender() {
|
||||||
const [sorting, setSorting] = useState<SortState | null>({
|
const [sorting, setSorting] = useState<SortState | null>({
|
||||||
field: 'name',
|
field: 'name',
|
||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
--bg-page: #16121a;
|
--bg-page: #16121a;
|
||||||
--surface-bg: rgba(24, 24, 27, 0.45);
|
--surface-bg: rgba(24, 24, 27, 0.45);
|
||||||
--surface-bg-strong: rgba(24, 24, 27, 0.62);
|
--surface-bg-strong: rgba(24, 24, 27, 0.62);
|
||||||
|
--datepicker-menu-bg: #18181b;
|
||||||
--surface-border: rgba(82, 82, 91, 0.6);
|
--surface-border: rgba(82, 82, 91, 0.6);
|
||||||
--surface-divider: rgba(63, 63, 70, 0.85);
|
--surface-divider: rgba(63, 63, 70, 0.85);
|
||||||
--text-primary: #d5cfdf;
|
--text-primary: #d5cfdf;
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
--field-disabled-border: #3f3f46;
|
--field-disabled-border: #3f3f46;
|
||||||
--field-disabled-text: #bbb6c3;
|
--field-disabled-text: #bbb6c3;
|
||||||
--field-disabled-placeholder: #71717a;
|
--field-disabled-placeholder: #71717a;
|
||||||
|
--field-selection-bg: rgb(var(--accent-500) / 0.42);
|
||||||
|
--field-selection-text: var(--text-primary);
|
||||||
--ghost-bg: rgba(24, 24, 27, 0.5);
|
--ghost-bg: rgba(24, 24, 27, 0.5);
|
||||||
--ghost-border: #3f3f46;
|
--ghost-border: #3f3f46;
|
||||||
--ghost-hover: rgba(39, 39, 42, 0.7);
|
--ghost-hover: rgba(39, 39, 42, 0.7);
|
||||||
@@ -57,6 +60,7 @@
|
|||||||
--bg-page: #f7f7fb;
|
--bg-page: #f7f7fb;
|
||||||
--surface-bg: rgba(255, 255, 255, 0.9);
|
--surface-bg: rgba(255, 255, 255, 0.9);
|
||||||
--surface-bg-strong: rgba(255, 255, 255, 0.98);
|
--surface-bg-strong: rgba(255, 255, 255, 0.98);
|
||||||
|
--datepicker-menu-bg: #ffffff;
|
||||||
--surface-border: rgba(161, 161, 170, 0.45);
|
--surface-border: rgba(161, 161, 170, 0.45);
|
||||||
--surface-divider: rgba(212, 212, 216, 0.9);
|
--surface-divider: rgba(212, 212, 216, 0.9);
|
||||||
--text-primary: #52485c;
|
--text-primary: #52485c;
|
||||||
@@ -69,6 +73,8 @@
|
|||||||
--field-disabled-border: #d7d7d7;
|
--field-disabled-border: #d7d7d7;
|
||||||
--field-disabled-text: #71717a;
|
--field-disabled-text: #71717a;
|
||||||
--field-disabled-placeholder: #a1a1aa;
|
--field-disabled-placeholder: #a1a1aa;
|
||||||
|
--field-selection-bg: rgb(var(--accent-500) / 0.24);
|
||||||
|
--field-selection-text: var(--text-primary);
|
||||||
--ghost-bg: rgba(255, 255, 255, 0.88);
|
--ghost-bg: rgba(255, 255, 255, 0.88);
|
||||||
--ghost-border: #d4d4d8;
|
--ghost-border: #d4d4d8;
|
||||||
--ghost-hover: #f4f4f5;
|
--ghost-hover: #f4f4f5;
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
color: var(--text-soft);
|
color: var(--text-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field::selection {
|
||||||
|
background-color: var(--field-selection-bg);
|
||||||
|
color: var(--field-selection-text);
|
||||||
|
-webkit-text-fill-color: var(--field-selection-text);
|
||||||
|
}
|
||||||
|
|
||||||
.field:disabled {
|
.field:disabled {
|
||||||
border-color: var(--field-disabled-border);
|
border-color: var(--field-disabled-border);
|
||||||
background-color: var(--field-disabled-bg);
|
background-color: var(--field-disabled-bg);
|
||||||
@@ -321,3 +327,190 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@apply px-4 py-3 text-sm;
|
@apply px-4 py-3 text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply absolute inset-y-0 right-2 my-auto inline-flex h-6 w-6 items-center justify-center rounded-md border border-transparent bg-transparent p-0 transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: rgb(var(--accent-400));
|
||||||
|
box-shadow: 0 0 0 2px rgb(var(--accent-400) / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-icon-btn:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup {
|
||||||
|
border: 1px solid var(--surface-divider);
|
||||||
|
background-color: var(--surface-bg-strong);
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
color: var(--text-primary);
|
||||||
|
backdrop-filter: saturate(145%) blur(var(--auth-glass-blur));
|
||||||
|
-webkit-backdrop-filter: saturate(145%) blur(var(--auth-glass-blur));
|
||||||
|
will-change: backdrop-filter;
|
||||||
|
max-width: min(96vw, 440px);
|
||||||
|
@apply fixed z-[70] flex flex-col gap-3 rounded-xl p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup-top {
|
||||||
|
transform-origin: bottom center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-popup-bottom {
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
.datepicker-popup {
|
||||||
|
@apply flex-row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-panel {
|
||||||
|
@apply min-w-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-calendar-nav {
|
||||||
|
@apply mb-2 flex items-center justify-between gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-nav-btn {
|
||||||
|
border: 1px solid var(--ghost-border);
|
||||||
|
background-color: var(--ghost-bg);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply inline-flex h-8 w-8 items-center justify-center rounded-lg transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-nav-btn:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-heading-controls {
|
||||||
|
@apply relative flex items-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-btn {
|
||||||
|
border: 1px solid var(--field-border);
|
||||||
|
background-color: var(--field-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
@apply inline-flex h-8 items-center rounded-lg px-2.5 text-xs font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-menu {
|
||||||
|
border: 1px solid var(--surface-divider);
|
||||||
|
background-color: var(--datepicker-menu-bg);
|
||||||
|
box-shadow: var(--shadow-glow);
|
||||||
|
@apply absolute left-0 top-full z-20 mt-1 max-h-48 min-w-[9rem] overflow-y-auto rounded-lg p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply block w-full rounded-md px-2 py-1.5 text-left text-xs font-medium transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-chooser-option.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500) / 0.22);
|
||||||
|
color: rgb(var(--accent-300));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-weekdays {
|
||||||
|
@apply mb-1 grid grid-cols-7 gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-weekday {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply text-center text-[0.65rem] font-semibold uppercase tracking-[0.08em];
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-grid {
|
||||||
|
@apply grid grid-cols-7 gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply inline-flex h-8 w-8 items-center justify-center rounded-lg text-sm transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500));
|
||||||
|
color: rgb(var(--accent-contrast));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-today {
|
||||||
|
border: 1px solid rgb(var(--accent-400) / 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day.is-outside-month {
|
||||||
|
color: var(--text-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-day:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-root {
|
||||||
|
@apply grid min-w-[180px] grid-cols-2 gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-column {
|
||||||
|
@apply flex min-w-0 flex-col gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-title {
|
||||||
|
color: var(--text-muted);
|
||||||
|
@apply text-[0.65rem] font-semibold uppercase tracking-[0.08em];
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-list {
|
||||||
|
border: 1px solid var(--field-border);
|
||||||
|
background-color: var(--field-bg);
|
||||||
|
@apply max-h-52 overflow-y-auto rounded-lg p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
@apply block w-full rounded-md px-2 py-1.5 text-left text-xs font-semibold transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option:hover {
|
||||||
|
background-color: var(--ghost-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option.is-selected {
|
||||||
|
background-color: rgb(var(--accent-500) / 0.22);
|
||||||
|
color: rgb(var(--accent-300));
|
||||||
|
}
|
||||||
|
|
||||||
|
.datepicker-time-option:disabled {
|
||||||
|
color: var(--ghost-disabled-text);
|
||||||
|
background-color: transparent;
|
||||||
|
@apply cursor-not-allowed opacity-55;
|
||||||
|
}
|
||||||
|
|||||||
77
tests/components/Button.test.tsx
Normal file
77
tests/components/Button.test.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { HomeIcon } from '@heroicons/react/24/solid';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { Button } from '../../src/components/Button';
|
||||||
|
import { renderWithRouter } from '../helpers/renderWithRouter';
|
||||||
|
|
||||||
|
describe('Button', () => {
|
||||||
|
it('renders native button with expected type and disabled state', () => {
|
||||||
|
render(<Button label="Save" type="solid" htmlType="submit" disabled />);
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: 'Save' });
|
||||||
|
expect(button.tagName).toBe('BUTTON');
|
||||||
|
expect(button).toHaveAttribute('type', 'submit');
|
||||||
|
expect(button).toBeDisabled();
|
||||||
|
expect(button).toHaveClass('btn-solid');
|
||||||
|
expect(button).toHaveClass('btn-primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults non-solid button variants to secondary', () => {
|
||||||
|
render(<Button label="Details" type="noborder" />);
|
||||||
|
expect(screen.getByRole('button', { name: 'Details' })).toHaveClass('btn-secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses explicit variant when provided', () => {
|
||||||
|
render(<Button label="Danger" type="outlined" variant="important" />);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Danger' })).toHaveClass('btn-important');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders icon-only button and custom aria label', () => {
|
||||||
|
render(<Button type="solid" icon={HomeIcon} ariaLabel="Open home" />);
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: 'Open home' });
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
expect(button).toHaveClass('!p-0');
|
||||||
|
expect(button.textContent).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders link button and prevents click when disabled', () => {
|
||||||
|
const onClick = vi.fn();
|
||||||
|
renderWithRouter(
|
||||||
|
<Button label="Go home" type="outlined" to="/home" onClick={onClick} disabled />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link', { name: 'Go home' });
|
||||||
|
fireEvent.click(link);
|
||||||
|
|
||||||
|
expect(link).toHaveAttribute('aria-disabled', 'true');
|
||||||
|
expect(link).toHaveAttribute('tabindex', '-1');
|
||||||
|
expect(onClick).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onClick for enabled link buttons', () => {
|
||||||
|
const onClick = vi.fn();
|
||||||
|
renderWithRouter(<Button label="Profile" type="solid" to="/profile" onClick={onClick} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('link', { name: 'Profile' }));
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders icon+label spacing and custom class names', () => {
|
||||||
|
render(
|
||||||
|
<Button
|
||||||
|
label="Home"
|
||||||
|
type="outlined"
|
||||||
|
icon={HomeIcon}
|
||||||
|
className="custom-button"
|
||||||
|
width="full"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = screen.getByRole('button', { name: 'Home' });
|
||||||
|
expect(button).toHaveClass('gap-1.5');
|
||||||
|
expect(button).toHaveClass('custom-button');
|
||||||
|
expect(button).toHaveClass('w-full');
|
||||||
|
});
|
||||||
|
});
|
||||||
62
tests/components/Chip.test.tsx
Normal file
62
tests/components/Chip.test.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { Chip } from '../../src/components/Chip';
|
||||||
|
|
||||||
|
describe('Chip', () => {
|
||||||
|
it('renders default span with solid classes', () => {
|
||||||
|
render(<Chip>Default</Chip>);
|
||||||
|
|
||||||
|
const chip = screen.getByText('Default');
|
||||||
|
expect(chip.tagName).toBe('SPAN');
|
||||||
|
expect(chip.className).toContain('chip-solid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports outlined variant with valid tone', () => {
|
||||||
|
render(
|
||||||
|
<Chip as="div" variant="outlined" tone="indigo-700">
|
||||||
|
Custom
|
||||||
|
</Chip>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const chip = screen.getByText('Custom');
|
||||||
|
expect(chip.tagName).toBe('DIV');
|
||||||
|
expect(chip.className).toContain('chip-outlined');
|
||||||
|
expect(chip).toHaveStyle({
|
||||||
|
borderColor: 'rgb(67, 56, 202)',
|
||||||
|
color: 'rgb(67, 56, 202)',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports direct tone tokens without shades for solid variant', () => {
|
||||||
|
render(<Chip tone="white">Solid</Chip>);
|
||||||
|
|
||||||
|
expect(screen.getByText('Solid')).toHaveStyle({
|
||||||
|
borderColor: 'rgb(255, 255, 255)',
|
||||||
|
backgroundColor: 'rgb(255, 255, 255)',
|
||||||
|
color: 'rgb(255, 255, 255)',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores invalid/empty tones and keeps className', () => {
|
||||||
|
const { rerender } = render(
|
||||||
|
<Chip tone="not-a-token" className="chip-custom">
|
||||||
|
Invalid
|
||||||
|
</Chip>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const invalid = screen.getByText('Invalid');
|
||||||
|
expect(invalid).toHaveClass('chip-custom');
|
||||||
|
expect(invalid.getAttribute('style')).toBeNull();
|
||||||
|
|
||||||
|
rerender(<Chip tone=" ">Blank</Chip>);
|
||||||
|
expect(screen.getByText('Blank').getAttribute('style')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores unresolved direct palettes and unknown shades', () => {
|
||||||
|
const { rerender } = render(<Chip tone="indigo">Palette token</Chip>);
|
||||||
|
expect(screen.getByText('Palette token').getAttribute('style')).toBeNull();
|
||||||
|
|
||||||
|
rerender(<Chip tone="indigo-999">Unknown shade</Chip>);
|
||||||
|
expect(screen.getByText('Unknown shade').getAttribute('style')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
428
tests/components/DatePicker.logic.test.ts
Normal file
428
tests/components/DatePicker.logic.test.ts
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { __datePickerTestUtils as utils } from '../../src/components/DatePicker';
|
||||||
|
|
||||||
|
type GlobalDescriptor = PropertyDescriptor | undefined;
|
||||||
|
|
||||||
|
function setGlobalProperty(name: string, descriptor: PropertyDescriptor): GlobalDescriptor {
|
||||||
|
const key = name as keyof typeof globalThis;
|
||||||
|
const original = Object.getOwnPropertyDescriptor(globalThis, key);
|
||||||
|
Object.defineProperty(globalThis, key, descriptor);
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreGlobalProperty(name: string, original: GlobalDescriptor): void {
|
||||||
|
const key = name as keyof typeof globalThis;
|
||||||
|
if (original) {
|
||||||
|
Object.defineProperty(globalThis, key, original);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reflect.deleteProperty(globalThis, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DatePicker logic helpers', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats and clamps numeric values', () => {
|
||||||
|
expect(utils.pad2(3)).toBe('03');
|
||||||
|
expect(utils.pad4(12)).toBe('0012');
|
||||||
|
expect(utils.clampNumber(99, 0, 50)).toBe(50);
|
||||||
|
expect(utils.clampNumber(-1, 0, 50)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates calendar dates strictly', () => {
|
||||||
|
expect(utils.createValidatedDate(2026, 2, 29)).toBeNull();
|
||||||
|
expect(utils.createValidatedDate(2028, 2, 29)).toBeInstanceOf(Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves locale with and without navigator', () => {
|
||||||
|
const originalNavigator = setGlobalProperty('navigator', {
|
||||||
|
configurable: true,
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
expect(utils.resolveLocale()).toBe('en-US');
|
||||||
|
restoreGlobalProperty('navigator', originalNavigator);
|
||||||
|
|
||||||
|
const locale = utils.resolveLocale();
|
||||||
|
expect(typeof locale).toBe('string');
|
||||||
|
expect(locale.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves week start across locale and fallback branches', () => {
|
||||||
|
const originalIntl = setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 2 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('de-DE')).toBe(2);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 7 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 0 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
weekInfo = { firstDay: 'x' as unknown as number };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
setGlobalProperty('Intl', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
Locale: class MockLocale {
|
||||||
|
constructor() {
|
||||||
|
throw new Error('boom');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(utils.resolveWeekStart('en-US')).toBe(0);
|
||||||
|
|
||||||
|
restoreGlobalProperty('Intl', originalIntl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds and validates format configuration', () => {
|
||||||
|
expect(utils.buildFormatConfigOrNull('date-time', '')).toBeNull();
|
||||||
|
expect(utils.buildFormatConfigOrNull('date-time', 'yyyy/mm/dd HH')).toBeNull();
|
||||||
|
|
||||||
|
const config = utils.buildFormatConfigOrNull('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
expect(config).not.toBeNull();
|
||||||
|
expect(config?.segments).toHaveLength(5);
|
||||||
|
expect(config?.totalLength).toBe(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to default format when requested format is invalid', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'yyyy/mm/dd');
|
||||||
|
expect(config.format).toBe('yyyy/mm/dd HH:mm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses valid values and rejects invalid input', () => {
|
||||||
|
const dateTimeConfig = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
expect(utils.parsePickerValueWithFormat('22/02/2026 14:30', dateTimeConfig)).toEqual({
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 22),
|
||||||
|
hour: 14,
|
||||||
|
minute: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(utils.parsePickerValueWithFormat('2/2/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('22-02-2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('aa/02/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('31/02/2026 14:30', dateTimeConfig)).toBeNull();
|
||||||
|
expect(utils.parsePickerValueWithFormat('22/02/2026 24:30', dateTimeConfig)).toBeNull();
|
||||||
|
|
||||||
|
const timeConfig = utils.buildFormatConfig('time', 'HH:mm');
|
||||||
|
expect(utils.parsePickerValueWithFormat('09:00', timeConfig)).toEqual({
|
||||||
|
date: utils.startOfDay(new Date()),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
});
|
||||||
|
expect(utils.parsePickerValueWithFormat('09:77', timeConfig)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats picker values with the chosen configuration', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
const text = utils.formatPickerValueWithFormat(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2027, 2, 11),
|
||||||
|
hour: 6,
|
||||||
|
minute: 5,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(text).toBe('11/03/2027 06:05');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compares values by picker type', () => {
|
||||||
|
const date = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 23,
|
||||||
|
minute: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 2),
|
||||||
|
hour: 1,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
'date',
|
||||||
|
);
|
||||||
|
expect(date).toBeLessThan(0);
|
||||||
|
|
||||||
|
const time = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 8,
|
||||||
|
minute: 59,
|
||||||
|
},
|
||||||
|
'time',
|
||||||
|
);
|
||||||
|
expect(time).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const dateTime = utils.comparePickerValue(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 1,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 1),
|
||||||
|
hour: 1,
|
||||||
|
minute: 1,
|
||||||
|
},
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(dateTime).toBeLessThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes and enforces picker ranges', () => {
|
||||||
|
const min = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 9,
|
||||||
|
minute: 0,
|
||||||
|
};
|
||||||
|
const max = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalized = utils.normalizeRange(max, min, 'date-time');
|
||||||
|
expect(normalized.minValue).toEqual(min);
|
||||||
|
expect(normalized.maxValue).toEqual(max);
|
||||||
|
|
||||||
|
const below = utils.clampPickerToRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 8,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(below).toEqual(min);
|
||||||
|
|
||||||
|
const above = utils.clampPickerToRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 30,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
);
|
||||||
|
expect(above).toEqual(max);
|
||||||
|
|
||||||
|
expect(utils.isWithinRange(min, min, max, 'date-time')).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isWithinRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 7,
|
||||||
|
minute: 59,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isWithinRange(
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 1,
|
||||||
|
},
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
'date-time',
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies segment edits across year, month, day, hour and minute', () => {
|
||||||
|
const base = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 1, 28),
|
||||||
|
hour: 14,
|
||||||
|
minute: 30,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(utils.applySegmentDigits(base, 'year', '0001').date.getFullYear()).toBe(1);
|
||||||
|
expect(utils.applySegmentDigits(base, 'month', '13').date.getMonth()).toBe(11);
|
||||||
|
expect(utils.applySegmentDigits(base, 'day', '99').date.getDate()).toBe(28);
|
||||||
|
expect(utils.applySegmentDigits(base, 'hour', '88').hour).toBe(23);
|
||||||
|
expect(utils.applySegmentDigits(base, 'minute', '99').minute).toBe(59);
|
||||||
|
|
||||||
|
const unchanged = utils.applySegmentDigits(base, 'day', 'not-a-number');
|
||||||
|
expect(unchanged).toEqual(base);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves segment index from caret position', () => {
|
||||||
|
const config = utils.buildFormatConfig('date-time', 'dd/mm/yyyy HH:mm');
|
||||||
|
|
||||||
|
expect(utils.findSegmentIndexByCaret([], 3)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, null)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 0)).toBe(0);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 6)).toBe(2);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 99)).toBe(
|
||||||
|
config.segments[config.segments.length - 1].segmentIndex,
|
||||||
|
);
|
||||||
|
expect(utils.findSegmentIndexByCaret(config.segments, 2)).toBe(0);
|
||||||
|
|
||||||
|
const nonStandardSegments = [
|
||||||
|
{
|
||||||
|
type: 'segment',
|
||||||
|
kind: 'day',
|
||||||
|
token: 'dd',
|
||||||
|
length: 2,
|
||||||
|
start: 5,
|
||||||
|
end: 7,
|
||||||
|
segmentIndex: 0,
|
||||||
|
},
|
||||||
|
] as unknown as Parameters<typeof utils.findSegmentIndexByCaret>[0];
|
||||||
|
expect(utils.findSegmentIndexByCaret(nonStandardSegments, 1)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks date and hour selectability inside constrained ranges', () => {
|
||||||
|
const min = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 9,
|
||||||
|
minute: 30,
|
||||||
|
};
|
||||||
|
const max = {
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 10,
|
||||||
|
minute: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 9),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 11),
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDateSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 9),
|
||||||
|
'date',
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
8,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
9,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
utils.isHourSelectableForRange(
|
||||||
|
utils.createDateAtLocalMidnight(2026, 2, 10),
|
||||||
|
11,
|
||||||
|
'date-time',
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds month grids and joins class names', () => {
|
||||||
|
const month = utils.createDateAtLocalMidnight(2026, 2, 10);
|
||||||
|
const grid = utils.buildMonthGrid(month, 1);
|
||||||
|
expect(grid).toHaveLength(42);
|
||||||
|
expect(grid[0]).toBeInstanceOf(Date);
|
||||||
|
expect(grid[41]).toBeInstanceOf(Date);
|
||||||
|
|
||||||
|
expect(utils.joinClassNames('a', false, 'b', undefined, '', null, 'c')).toBe('a b c');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns refs for callback and object refs', () => {
|
||||||
|
const callback = vi.fn();
|
||||||
|
const objectRef = { current: null as HTMLInputElement | null };
|
||||||
|
const node = document.createElement('input');
|
||||||
|
|
||||||
|
utils.assignRef(callback, node);
|
||||||
|
utils.assignRef(objectRef, node);
|
||||||
|
utils.assignRef(undefined, node);
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith(node);
|
||||||
|
expect(objectRef.current).toBe(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
740
tests/components/DatePicker.test.tsx
Normal file
740
tests/components/DatePicker.test.tsx
Normal file
@@ -0,0 +1,740 @@
|
|||||||
|
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import { createRef, type FocusEvent as ReactFocusEvent, useState } from 'react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { DatePicker } from '../../src/components/DatePicker';
|
||||||
|
|
||||||
|
type ControlledProps = {
|
||||||
|
type: 'date' | 'time' | 'date-time';
|
||||||
|
initialValue: string;
|
||||||
|
format?: string;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
onValueChange?: (value: string) => void;
|
||||||
|
onBlur?: (event: ReactFocusEvent<HTMLInputElement>) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ControlledDatePicker({
|
||||||
|
type,
|
||||||
|
initialValue,
|
||||||
|
format,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
onValueChange,
|
||||||
|
onBlur,
|
||||||
|
disabled = false,
|
||||||
|
}: Readonly<ControlledProps>) {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
format={format}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChange={(event) => {
|
||||||
|
setValue(event.target.value);
|
||||||
|
onValueChange?.(event.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickCurrentMonthDay(label: string): void {
|
||||||
|
const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
|
||||||
|
const targetButton = dayButtons.find(
|
||||||
|
(button) => button.textContent === label && !button.classList.contains('is-outside-month'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(targetButton).toBeDefined();
|
||||||
|
fireEvent.click(targetButton as HTMLButtonElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMonthDayButton(label: string): HTMLButtonElement {
|
||||||
|
const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
|
||||||
|
const targetButton = dayButtons.find(
|
||||||
|
(button) => button.textContent === label && !button.classList.contains('is-outside-month'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetButton) {
|
||||||
|
throw new Error(`Could not find day button for ${label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPasteEvent(text: string): Event {
|
||||||
|
const event = new Event('paste', { bubbles: true, cancelable: true });
|
||||||
|
Object.defineProperty(event, 'clipboardData', {
|
||||||
|
value: {
|
||||||
|
getData: () => text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRect(left: number, top: number, width: number, height: number): DOMRect {
|
||||||
|
return {
|
||||||
|
x: left,
|
||||||
|
y: top,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
right: left + width,
|
||||||
|
bottom: top + height,
|
||||||
|
toJSON: () => ({}),
|
||||||
|
} as DOMRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DatePicker', () => {
|
||||||
|
it('opens popup from icon button and closes with Escape', () => {
|
||||||
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.type).toBe('text');
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
expect(
|
||||||
|
screen.getByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.keyDown(document, { key: 'Escape' });
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Close date picker' }));
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports segment-by-segment editing with custom format and auto-advance', async () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('01/02/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/02/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '3' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2026 14:30');
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '2' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '2' });
|
||||||
|
fireEvent.keyDown(input, { key: '7' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 14:30');
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
fireEvent.keyDown(input, { key: '6' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 16:30');
|
||||||
|
expect(input.selectionStart).toBe(14);
|
||||||
|
expect(input.selectionEnd).toBe(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '4' });
|
||||||
|
fireEvent.keyDown(input, { key: '5' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('11/03/2027 16:45');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves year 0000 while editing and does not coerce to 19xx', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('22/02/0000 14:30');
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
fireEvent.keyDown(input, { key: '0' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value).toBe('22/02/0000 14:30');
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses separator keys to move between editable segments', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
expect(input.selectionEnd).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(6);
|
||||||
|
expect(input.selectionEnd).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ' ' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(11);
|
||||||
|
expect(input.selectionEnd).toBe(13);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ':' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(14);
|
||||||
|
expect(input.selectionEnd).toBe(16);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies min/max constraints to calendar and time options', () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
initialValue="2026/03/10 10:00"
|
||||||
|
min="2026/03/10 09:30"
|
||||||
|
max="2026/03/10 10:15"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(getCurrentMonthDayButton('9')).toBeDisabled();
|
||||||
|
expect(getCurrentMonthDayButton('10')).toBeEnabled();
|
||||||
|
expect(getCurrentMonthDayButton('11')).toBeDisabled();
|
||||||
|
|
||||||
|
const hoursList = screen.getByRole('listbox', { name: 'Hours' });
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '08' })).toBeDisabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '09' })).toBeEnabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '10' })).toBeEnabled();
|
||||||
|
expect(within(hoursList).getByRole('button', { name: '11' })).toBeDisabled();
|
||||||
|
|
||||||
|
const minutesList = screen.getByRole('listbox', { name: 'Minutes' });
|
||||||
|
expect(within(minutesList).getByRole('button', { name: '15' })).toBeEnabled();
|
||||||
|
expect(within(minutesList).getByRole('button', { name: '20' })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders date mode calendar only and auto-closes after day selection', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date"
|
||||||
|
initialValue="2031/05/20"
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date picker popup' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Hours')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Minutes')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
pickCurrentMonthDay('15');
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toMatch(/^\d{4}\/\d{2}\/15$/);
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('commits calendar date changes in date-time mode without closing the popup', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
initialValue="2031/05/20 14:30"
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
pickCurrentMonthDay('15');
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toMatch(/^\d{4}\/\d{2}\/15 14:30$/);
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders time mode selectors only and keeps popup open after selection', () => {
|
||||||
|
const onValueChange = vi.fn();
|
||||||
|
render(<ControlledDatePicker type="time" initialValue="09:00" onValueChange={onValueChange} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('grid')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
const hoursList = screen.getByRole('listbox', { name: 'Hours' });
|
||||||
|
const minutesList = screen.getByRole('listbox', { name: 'Minutes' });
|
||||||
|
|
||||||
|
fireEvent.click(within(hoursList).getByRole('button', { name: '13' }));
|
||||||
|
fireEvent.click(within(minutesList).getByRole('button', { name: '45' }));
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
expect(input.value).toBe('13:45');
|
||||||
|
expect(onValueChange).toHaveBeenCalledTimes(2);
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks popup interactions while disabled', () => {
|
||||||
|
render(<ControlledDatePicker type="date" initialValue="2031/05/20" disabled />);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
const iconButton = screen.getByRole('button', { name: 'Open date picker' });
|
||||||
|
|
||||||
|
expect(input).toBeDisabled();
|
||||||
|
expect(iconButton).toBeDisabled();
|
||||||
|
|
||||||
|
fireEvent.click(iconButton);
|
||||||
|
|
||||||
|
expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders right icon and error message', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
value="2031/05/20 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
rightIcon={<span data-testid="right-icon">R</span>}
|
||||||
|
error="Invalid date"
|
||||||
|
inputClassName="custom-input"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector('input');
|
||||||
|
expect(input).toBeInstanceOf(HTMLInputElement);
|
||||||
|
expect(screen.getByTestId('right-icon')).toBeInTheDocument();
|
||||||
|
expect(input).toHaveClass('pr-10');
|
||||||
|
expect(input).toHaveClass('custom-input');
|
||||||
|
expect(screen.getByText('Invalid date')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards inputRef for callback and object refs', () => {
|
||||||
|
const callbackRef = vi.fn();
|
||||||
|
const objectRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
value="2031/05/20 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
inputRef={callbackRef}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(callbackRef).toHaveBeenCalled();
|
||||||
|
const callbackNode = callbackRef.mock.calls.find(([node]) => node instanceof HTMLInputElement)?.[0];
|
||||||
|
expect(callbackNode).toBeInstanceOf(HTMLInputElement);
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
value="2031/05/20 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
inputRef={objectRef}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(objectRef.current).toBeInstanceOf(HTMLInputElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports inline layout', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<DatePicker label="Start time" type="time" value="09:00" onChange={() => {}} layout="inline" />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
||||||
|
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles outside vs inside pointer interactions for popup close behavior', () => {
|
||||||
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
const dialog = screen.getByRole('dialog', { name: 'Date and time picker popup' });
|
||||||
|
|
||||||
|
fireEvent.mouseDown(dialog);
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.mouseDown(document.body);
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports month/year chooser interactions and month navigation buttons', async () => {
|
||||||
|
render(<DatePicker label="Schedule" type="date" value="2031/05/20" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Previous month' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Next month' }));
|
||||||
|
|
||||||
|
const chooserButtons = document.querySelectorAll('.datepicker-chooser-btn');
|
||||||
|
const monthChooserButton = chooserButtons[0] as HTMLButtonElement;
|
||||||
|
const yearChooserButton = chooserButtons[1] as HTMLButtonElement;
|
||||||
|
|
||||||
|
const initialMonthText = monthChooserButton.textContent;
|
||||||
|
fireEvent.click(monthChooserButton);
|
||||||
|
const monthList = screen.getByRole('listbox', { name: 'Choose month' });
|
||||||
|
const monthOptions = within(monthList).getAllByRole('button');
|
||||||
|
const nextMonth = monthOptions.find((option) => option.textContent !== initialMonthText) ?? monthOptions[0];
|
||||||
|
fireEvent.click(nextMonth);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('listbox', { name: 'Choose month' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect((document.querySelectorAll('.datepicker-chooser-btn')[0] as HTMLButtonElement).textContent).toBe(
|
||||||
|
nextMonth.textContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialYearText = yearChooserButton.textContent;
|
||||||
|
fireEvent.click(yearChooserButton);
|
||||||
|
const yearList = screen.getByRole('listbox', { name: 'Choose year' });
|
||||||
|
const yearOptions = within(yearList).getAllByRole('button');
|
||||||
|
const nextYear = yearOptions.find((option) => option.textContent !== initialYearText) ?? yearOptions[0];
|
||||||
|
fireEvent.click(nextYear);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByRole('listbox', { name: 'Choose year' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect((document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement).textContent).toBe(
|
||||||
|
nextYear.textContent,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('guards month navigation at absolute calendar boundaries', () => {
|
||||||
|
const first = render(
|
||||||
|
<DatePicker label="Schedule" type="date" value="0000/01/15" onChange={() => {}} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Previous month' }));
|
||||||
|
const lowerYearButton = document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement;
|
||||||
|
expect(lowerYearButton.textContent).toBe('0');
|
||||||
|
|
||||||
|
first.unmount();
|
||||||
|
|
||||||
|
render(<DatePicker label="Schedule" type="date" value="9999/12/15" onChange={() => {}} />);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Next month' }));
|
||||||
|
const upperYearButton = document.querySelectorAll('.datepicker-chooser-btn')[1] as HTMLButtonElement;
|
||||||
|
expect(upperYearButton.textContent).toBe('9999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports keyboard navigation commands and segment reset shortcuts', async () => {
|
||||||
|
const onBlur = vi.fn();
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
onBlur={onBlur}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.selectionStart).toBe(0);
|
||||||
|
expect(input.selectionEnd).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowRight' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.startsWith('01/')).toBe(true);
|
||||||
|
expect(input.selectionStart).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.mouseUp(input);
|
||||||
|
fireEvent.click(input);
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowRight' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(3));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) <= 3).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Tab' });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) >= 0).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Tab', shiftKey: true });
|
||||||
|
await waitFor(() => expect((input.selectionStart ?? 0) >= 0).toBe(true));
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Backspace' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.startsWith('01/')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(3));
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(input.value.slice(3, 5)).toBe('01');
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: '/' });
|
||||||
|
await waitFor(() => expect(input.selectionStart).toBe(6));
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: ' ' });
|
||||||
|
fireEvent.keyDown(input, { key: 'Delete' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(/\s00:|:00$/.test(input.value)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.keyDown(input, { key: 'Enter' });
|
||||||
|
fireEvent.keyDown(input, { key: 'x' });
|
||||||
|
fireEvent.blur(input);
|
||||||
|
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles paste validation and value commits', async () => {
|
||||||
|
render(
|
||||||
|
<ControlledDatePicker
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
initialValue="22/02/2026 14:30"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
input.dispatchEvent(createPasteEvent('invalid value'));
|
||||||
|
});
|
||||||
|
expect(input.value).toBe('22/02/2026 14:30');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
input.dispatchEvent(createPasteEvent('11/03/2027 16:45'));
|
||||||
|
});
|
||||||
|
expect(input.value).toBe('11/03/2027 16:45');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns early from interaction handlers when disabled', () => {
|
||||||
|
render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
disabled
|
||||||
|
onChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
|
||||||
|
fireEvent.focus(input);
|
||||||
|
fireEvent.mouseUp(input);
|
||||||
|
fireEvent.click(input);
|
||||||
|
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||||
|
|
||||||
|
const disabledPaste = createPasteEvent('11/03/2027 16:45');
|
||||||
|
input.dispatchEvent(disabledPaste);
|
||||||
|
expect(disabledPaste.defaultPrevented).toBe(false);
|
||||||
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to direct assignment and synthetic onChange when native dispatch is stubbed', async () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
const originalGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
onChange={onChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
const nativeDispatchEvent = input.dispatchEvent.bind(input);
|
||||||
|
vi.spyOn(input, 'dispatchEvent').mockImplementation((event: Event) => {
|
||||||
|
if (event.type === 'change') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nativeDispatchEvent(event);
|
||||||
|
});
|
||||||
|
vi.spyOn(Object, 'getOwnPropertyDescriptor').mockImplementation((target, property) => {
|
||||||
|
if (target === HTMLInputElement.prototype && property === 'value') {
|
||||||
|
return {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get: originalGetOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.get,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalGetOwnPropertyDescriptor(target, property);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.focus(input);
|
||||||
|
input.setSelectionRange(0, 2);
|
||||||
|
fireEvent.keyDown(input, { key: '1' });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('safely handles pending segment selection during unmount', () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<DatePicker
|
||||||
|
label="Schedule"
|
||||||
|
type="date-time"
|
||||||
|
format="dd/mm/yyyy HH:mm"
|
||||||
|
value="22/02/2026 14:30"
|
||||||
|
onChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Schedule') as HTMLInputElement;
|
||||||
|
fireEvent.focus(input);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
vi.runAllTimers();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recalculates popup position on window resize and scroll', async () => {
|
||||||
|
const originalInnerWidth = Object.getOwnPropertyDescriptor(window, 'innerWidth');
|
||||||
|
const originalInnerHeight = Object.getOwnPropertyDescriptor(window, 'innerHeight');
|
||||||
|
const rectSpy = vi
|
||||||
|
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
|
||||||
|
.mockImplementation(function mockRect(this: HTMLElement) {
|
||||||
|
if (this.classList.contains('datepicker-popup')) {
|
||||||
|
return createRect(0, 0, 300, 200);
|
||||||
|
}
|
||||||
|
if (this.classList.contains('relative') && this.querySelector('input')) {
|
||||||
|
return createRect(500, 80, 120, 40);
|
||||||
|
}
|
||||||
|
return createRect(0, 0, 100, 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, 'innerWidth', { configurable: true, value: 600 });
|
||||||
|
Object.defineProperty(window, 'innerHeight', { configurable: true, value: 480 });
|
||||||
|
|
||||||
|
render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
|
||||||
|
const dialog = screen.getByRole('dialog', { name: 'Date and time picker popup' });
|
||||||
|
expect(dialog).toBeInTheDocument();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(dialog.style.left).toBe('292px');
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent(window, new Event('resize'));
|
||||||
|
fireEvent(window, new Event('scroll'));
|
||||||
|
expect(screen.getByRole('dialog', { name: 'Date and time picker popup' })).toBeInTheDocument();
|
||||||
|
expect(dialog.style.left).toBe('292px');
|
||||||
|
|
||||||
|
rectSpy.mockRestore();
|
||||||
|
if (originalInnerWidth) {
|
||||||
|
Object.defineProperty(window, 'innerWidth', originalInnerWidth);
|
||||||
|
}
|
||||||
|
if (originalInnerHeight) {
|
||||||
|
Object.defineProperty(window, 'innerHeight', originalInnerHeight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
60
tests/components/Dropdown.test.tsx
Normal file
60
tests/components/Dropdown.test.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { Dropdown } from '../../src/components/Dropdown';
|
||||||
|
|
||||||
|
const choices = [
|
||||||
|
{ id: 'USER', label: 'User' },
|
||||||
|
{ id: 'ADMIN', label: 'Admin' },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Dropdown', () => {
|
||||||
|
it('calls onChange with selected value', () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
render(<Dropdown label="Role" value="USER" choices={choices} onChange={onChange} />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('Role'), { target: { value: 'ADMIN' } });
|
||||||
|
expect(onChange).toHaveBeenCalledWith('ADMIN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports inline layout and disabled/required state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Dropdown
|
||||||
|
label="Rows"
|
||||||
|
value="10"
|
||||||
|
choices={[{ id: '10', label: '10' }]}
|
||||||
|
layout="inline"
|
||||||
|
disabled
|
||||||
|
required
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const select = screen.getByLabelText('Rows');
|
||||||
|
expect(select).toBeDisabled();
|
||||||
|
expect(select).toBeRequired();
|
||||||
|
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders error and custom class names', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Dropdown
|
||||||
|
label="Role"
|
||||||
|
value="USER"
|
||||||
|
choices={choices}
|
||||||
|
error="Role is invalid"
|
||||||
|
className="custom-wrapper"
|
||||||
|
selectClassName="custom-select"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const select = container.querySelector('select');
|
||||||
|
expect(select).toBeInstanceOf(HTMLSelectElement);
|
||||||
|
expect(screen.getByText('Role is invalid')).toBeInTheDocument();
|
||||||
|
expect(select).toHaveClass('custom-select');
|
||||||
|
expect(screen.getByText('Role').closest('label')).toHaveClass('custom-wrapper');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports rendering without a label', () => {
|
||||||
|
const { container } = render(<Dropdown value="USER" choices={choices} />);
|
||||||
|
expect(container.querySelector('label > span')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
28
tests/components/Form.test.tsx
Normal file
28
tests/components/Form.test.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { Form } from '../../src/components/Form';
|
||||||
|
|
||||||
|
describe('Form', () => {
|
||||||
|
it('renders title, title actions and children', () => {
|
||||||
|
render(
|
||||||
|
<Form title="User Details" titleBarRight={<button type="button">Action</button>}>
|
||||||
|
<div>Form child</div>
|
||||||
|
</Form>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('User Details')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Action' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Form child')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports custom class names and optional title actions', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Form title="No Actions" className="form-custom">
|
||||||
|
<div>Child</div>
|
||||||
|
</Form>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.firstElementChild).toHaveClass('form-custom');
|
||||||
|
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
97
tests/components/InputField.test.tsx
Normal file
97
tests/components/InputField.test.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { createRef } from 'react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { InputField } from '../../src/components/InputField';
|
||||||
|
|
||||||
|
describe('InputField', () => {
|
||||||
|
it('supports email type and emits change/blur callbacks', () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
const onBlur = vi.fn();
|
||||||
|
render(
|
||||||
|
<InputField
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
value=""
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
required
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByLabelText('Email') as HTMLInputElement;
|
||||||
|
expect(input.type).toBe('email');
|
||||||
|
expect(input).toBeRequired();
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: 'new@example.com' } });
|
||||||
|
fireEvent.blur(input);
|
||||||
|
expect(onChange).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes refs to input element', () => {
|
||||||
|
const inputRef = createRef<HTMLInputElement>();
|
||||||
|
render(<InputField label="Username" type="text" value="john" onChange={() => {}} inputRef={inputRef} />);
|
||||||
|
|
||||||
|
expect(inputRef.current).toBeInstanceOf(HTMLInputElement);
|
||||||
|
expect(inputRef.current?.value).toBe('john');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles password visibility', () => {
|
||||||
|
render(<InputField label="Password" type="password" value="abc" onChange={() => {}} />);
|
||||||
|
|
||||||
|
expect((screen.getByLabelText('Password') as HTMLInputElement).type).toBe('password');
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Show password' }));
|
||||||
|
expect((screen.getByLabelText('Password') as HTMLInputElement).type).toBe('text');
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Hide password' }));
|
||||||
|
expect((screen.getByLabelText('Password') as HTMLInputElement).type).toBe('password');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders rightIcon for non-password input and displays errors', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<InputField
|
||||||
|
label="Username"
|
||||||
|
type="text"
|
||||||
|
value="john"
|
||||||
|
onChange={() => {}}
|
||||||
|
rightIcon={<span data-testid="right-icon">R</span>}
|
||||||
|
error="Invalid username"
|
||||||
|
inputClassName="custom-input"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = container.querySelector('input');
|
||||||
|
expect(input).toBeInstanceOf(HTMLInputElement);
|
||||||
|
expect(input).toHaveClass('pr-10');
|
||||||
|
expect(input).toHaveClass('custom-input');
|
||||||
|
expect(screen.getByTestId('right-icon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Invalid username')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables password toggle when input is disabled', () => {
|
||||||
|
render(
|
||||||
|
<InputField
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
value="secret"
|
||||||
|
onChange={() => {}}
|
||||||
|
disabled
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Show password' })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports inline layout classes', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<InputField label="Username" type="text" value="" onChange={() => {}} layout="inline" />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('label')).toHaveClass('inline-flex');
|
||||||
|
expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports rendering without a label', () => {
|
||||||
|
const { container } = render(<InputField type="text" value="" onChange={() => {}} />);
|
||||||
|
expect(container.querySelector('label > span')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
25
tests/components/Label.test.tsx
Normal file
25
tests/components/Label.test.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { Label } from '../../src/components/Label';
|
||||||
|
|
||||||
|
describe('Label', () => {
|
||||||
|
it('uses default and variant-specific tags/classes', () => {
|
||||||
|
const { rerender } = render(<Label>Body</Label>);
|
||||||
|
expect(screen.getByText('Body').tagName).toBe('P');
|
||||||
|
expect(screen.getByText('Body')).toHaveClass('ui-body-primary');
|
||||||
|
|
||||||
|
rerender(<Label variant="h1">Title</Label>);
|
||||||
|
expect(screen.getByText('Title').tagName).toBe('H1');
|
||||||
|
expect(screen.getByText('Title')).toHaveClass('ui-title');
|
||||||
|
|
||||||
|
rerender(<Label variant="h4">Section</Label>);
|
||||||
|
expect(screen.getByText('Section').tagName).toBe('H3');
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Label variant="h2" as="span">
|
||||||
|
Custom
|
||||||
|
</Label>,
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Custom').tagName).toBe('SPAN');
|
||||||
|
});
|
||||||
|
});
|
||||||
82
tests/components/MDXEditorField.test.tsx
Normal file
82
tests/components/MDXEditorField.test.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { MDXEditorField } from '../../src/components/MDXEditorField';
|
||||||
|
|
||||||
|
describe('MDXEditorField', () => {
|
||||||
|
it('renders label and change handler in editable mode', () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MDXEditorField
|
||||||
|
label="Content"
|
||||||
|
markdown=""
|
||||||
|
onChange={onChange}
|
||||||
|
themeClassName="light-theme"
|
||||||
|
editorClassName="extra-editor"
|
||||||
|
plugins={[]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Content')).toHaveClass('ui-label');
|
||||||
|
fireEvent.change(screen.getByLabelText('Markdown Editor'), {
|
||||||
|
target: { value: '# Hello' },
|
||||||
|
});
|
||||||
|
expect(onChange).toHaveBeenCalledWith('# Hello');
|
||||||
|
expect(screen.getByLabelText('Markdown Editor')).toHaveAttribute(
|
||||||
|
'data-class-name',
|
||||||
|
'light-theme extra-editor',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders preview and disabled classes when disabled', () => {
|
||||||
|
render(
|
||||||
|
<MDXEditorField
|
||||||
|
label="Content"
|
||||||
|
markdown="Disabled content"
|
||||||
|
disabled
|
||||||
|
themeClassName="dark-theme"
|
||||||
|
plugins={[]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const label = screen.getByText('Content');
|
||||||
|
expect(label).toHaveClass('ui-label-disabled');
|
||||||
|
expect(screen.getByTestId('md-preview')).toHaveTextContent('Disabled content');
|
||||||
|
expect(screen.getByTestId('md-preview')).toHaveAttribute('data-class-name', 'dark-theme');
|
||||||
|
expect(screen.queryByLabelText('Markdown Editor')).not.toBeInTheDocument();
|
||||||
|
expect(document.querySelector('.post-mdx-editor--disabled')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders read-only preview without label when label is omitted', () => {
|
||||||
|
render(
|
||||||
|
<MDXEditorField
|
||||||
|
markdown="Read only content"
|
||||||
|
readOnly
|
||||||
|
themeClassName="dark-theme"
|
||||||
|
plugins={[]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText('Content')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('md-preview')).toHaveTextContent('Read only content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports wrapper style/class overrides and error rendering', () => {
|
||||||
|
render(
|
||||||
|
<MDXEditorField
|
||||||
|
label="Content"
|
||||||
|
markdown=""
|
||||||
|
themeClassName="light-theme"
|
||||||
|
plugins={[]}
|
||||||
|
editorWrapperClassName="editor-wrapper"
|
||||||
|
editorWrapperStyle={{ borderWidth: '2px' }}
|
||||||
|
error="Content is required"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrapper = document.querySelector('.editor-wrapper');
|
||||||
|
expect(wrapper).toHaveClass('post-mdx-editor--enabled');
|
||||||
|
expect(wrapper).toHaveStyle({ borderWidth: '2px' });
|
||||||
|
expect(screen.getByText('Content is required')).toHaveClass('ui-error');
|
||||||
|
});
|
||||||
|
});
|
||||||
55
tests/components/SidebarNavItem.test.tsx
Normal file
55
tests/components/SidebarNavItem.test.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { UserCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { SidebarNavItem } from '../../src/components/SidebarNavItem';
|
||||||
|
import { renderWithRouter } from '../helpers/renderWithRouter';
|
||||||
|
|
||||||
|
describe('SidebarNavItem', () => {
|
||||||
|
it('renders active style and collapsed label behavior', () => {
|
||||||
|
renderWithRouter(
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path="/users"
|
||||||
|
element={<SidebarNavItem to="/users" label="Users" icon={UserCircleIcon} collapsed />}
|
||||||
|
/>
|
||||||
|
</Routes>,
|
||||||
|
{ route: '/users' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link', { name: 'Users' });
|
||||||
|
expect(link.className).toContain('ui-accent-active');
|
||||||
|
expect(link.className).toContain('mx-auto');
|
||||||
|
expect(screen.getByText('Users').className).toContain('lg:hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders inactive style and triggers onClick', () => {
|
||||||
|
const onClick = vi.fn();
|
||||||
|
renderWithRouter(
|
||||||
|
<Routes>
|
||||||
|
<Route path="/users" element={<div>Users page</div>} />
|
||||||
|
<Route
|
||||||
|
path="/profile"
|
||||||
|
element={
|
||||||
|
<SidebarNavItem
|
||||||
|
to="/users"
|
||||||
|
label="Users"
|
||||||
|
icon={UserCircleIcon}
|
||||||
|
collapsed={false}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>,
|
||||||
|
{ route: '/profile' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const link = screen.getByRole('link', { name: 'Users' });
|
||||||
|
expect(link.className).toContain('hover:bg-zinc-500/15');
|
||||||
|
expect(link.className).toContain('lg:w-full');
|
||||||
|
expect(screen.getByText('Users').className).toContain('truncate');
|
||||||
|
|
||||||
|
fireEvent.click(link);
|
||||||
|
expect(onClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
199
tests/components/Table.test.tsx
Normal file
199
tests/components/Table.test.tsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import { Table, type TableHeader } from '../../src/components/Table';
|
||||||
|
import type { SortState } from '../../src/types/sort';
|
||||||
|
|
||||||
|
type Row = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const headers: TableHeader<Row>[] = [
|
||||||
|
{ label: 'Name', id: 'name', value: (row) => row.name, headerClassName: 'head-name' },
|
||||||
|
{ label: 'Email', id: 'email', value: (row) => row.email, cellClassName: 'cell-email' },
|
||||||
|
{ label: 'Static', id: 'static', value: 'Always', sortable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
const data: Row[] = [{ id: '1', name: 'Jane', email: 'jane@example.com' }];
|
||||||
|
|
||||||
|
describe('Table', () => {
|
||||||
|
it('renders loading state', () => {
|
||||||
|
render(<Table headers={headers} data={[]} isLoading rowKey={(row) => row.id} />);
|
||||||
|
|
||||||
|
expect(document.querySelector('.animate-spin')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders empty state message when no rows are available', () => {
|
||||||
|
render(
|
||||||
|
<Table
|
||||||
|
headers={headers}
|
||||||
|
data={[]}
|
||||||
|
emptyMessage="Nothing here"
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Nothing here')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders row values from function and static header values', () => {
|
||||||
|
const rowKey = vi.fn((row: Row) => row.id);
|
||||||
|
render(<Table headers={headers} data={data} rowKey={rowKey} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Jane')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('jane@example.com')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Always')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Name').closest('th')).toHaveClass('head-name');
|
||||||
|
expect(screen.getByText('jane@example.com').closest('td')).toHaveClass('cell-email');
|
||||||
|
expect(rowKey).toHaveBeenCalledWith(data[0], 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows sortable buttons only when sort config is complete', () => {
|
||||||
|
const onSortChange = vi.fn();
|
||||||
|
const sortableHeaders: TableHeader<Row>[] = [
|
||||||
|
{ label: 'No field', id: 'a', sortable: true, value: (row) => row.name },
|
||||||
|
{ label: 'Empty field', id: 'b', sortable: true, sortField: '', value: (row) => row.name },
|
||||||
|
{ label: 'Name', id: 'c', sortable: true, sortField: 'name', value: (row) => row.name },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<Table headers={sortableHeaders} data={data} rowKey={(row) => row.id} onSortChange={onSortChange} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByRole('button', { name: 'Sort by No field' })).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('button', { name: 'Sort by Empty field' })).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Sort by Name' })).toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(<Table headers={sortableHeaders} data={data} rowKey={(row) => row.id} />);
|
||||||
|
expect(screen.queryByRole('button', { name: 'Sort by Name' })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders sort states and notifies callback on header click', () => {
|
||||||
|
const onSortChange = vi.fn();
|
||||||
|
const sortableHeaders: TableHeader<Row>[] = [
|
||||||
|
{ label: 'Name', id: 'name', sortable: true, sortField: 'name', value: (row) => row.name },
|
||||||
|
{ label: 'Email', id: 'email', sortable: true, sortField: 'email', value: (row) => row.email },
|
||||||
|
];
|
||||||
|
const sorting: SortState = { field: 'name', direction: 'asc' };
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<Table
|
||||||
|
headers={sortableHeaders}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
sorting={sorting}
|
||||||
|
onSortChange={onSortChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const nameSort = screen.getByRole('button', { name: 'Sort by Name' });
|
||||||
|
const emailSort = screen.getByRole('button', { name: 'Sort by Email' });
|
||||||
|
expect(nameSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'asc');
|
||||||
|
expect(emailSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'none');
|
||||||
|
|
||||||
|
fireEvent.click(nameSort);
|
||||||
|
expect(onSortChange).toHaveBeenCalledWith('name');
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Table
|
||||||
|
headers={sortableHeaders}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
sorting={{ field: 'name', direction: 'desc' }}
|
||||||
|
onSortChange={onSortChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(nameSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'desc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports pagination controls and page-size changes', () => {
|
||||||
|
const onPageChange = vi.fn();
|
||||||
|
const onPageSizeChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Table
|
||||||
|
headers={headers}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
pagination={{
|
||||||
|
page: 2,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 21,
|
||||||
|
totalPages: 3,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Previous page' }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Next page' }));
|
||||||
|
fireEvent.change(screen.getByLabelText('Rows'), { target: { value: '20' } });
|
||||||
|
|
||||||
|
expect(onPageChange).toHaveBeenNthCalledWith(1, 1);
|
||||||
|
expect(onPageChange).toHaveBeenNthCalledWith(2, 3);
|
||||||
|
expect(onPageSizeChange).toHaveBeenCalledWith(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides rows selector when onPageSizeChange is absent and clamps page count display', () => {
|
||||||
|
render(
|
||||||
|
<Table
|
||||||
|
headers={headers}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
pagination={{
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 1,
|
||||||
|
totalPages: 0,
|
||||||
|
onPageChange: vi.fn(),
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByLabelText('Rows')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Page 1 of 1')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables prev/next at bounds or while loading', () => {
|
||||||
|
const { rerender } = render(
|
||||||
|
<Table
|
||||||
|
headers={headers}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
pagination={{
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 10,
|
||||||
|
totalPages: 1,
|
||||||
|
onPageChange: vi.fn(),
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Previous page' })).toBeDisabled();
|
||||||
|
expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Table
|
||||||
|
headers={headers}
|
||||||
|
data={data}
|
||||||
|
rowKey={(row) => row.id}
|
||||||
|
isLoading
|
||||||
|
pagination={{
|
||||||
|
page: 2,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 100,
|
||||||
|
totalPages: 10,
|
||||||
|
onPageChange: vi.fn(),
|
||||||
|
onPageSizeChange: vi.fn(),
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Previous page' })).toBeDisabled();
|
||||||
|
expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled();
|
||||||
|
expect(screen.getByLabelText('Rows')).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
12
tests/helpers/renderWithRouter.tsx
Normal file
12
tests/helpers/renderWithRouter.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
|
type RenderWithRouterOptions = {
|
||||||
|
route?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function renderWithRouter(ui: ReactElement, options: RenderWithRouterOptions = {}) {
|
||||||
|
const { route = '/' } = options;
|
||||||
|
return render(<MemoryRouter initialEntries={[route]}>{ui}</MemoryRouter>);
|
||||||
|
}
|
||||||
16
tests/index.test.ts
Normal file
16
tests/index.test.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import * as webUi from '../src/index';
|
||||||
|
|
||||||
|
describe('index exports', () => {
|
||||||
|
it('exposes runtime component exports', () => {
|
||||||
|
expect(typeof webUi.Button).toBe('function');
|
||||||
|
expect(typeof webUi.Chip).toBe('function');
|
||||||
|
expect(typeof webUi.DatePicker).toBe('function');
|
||||||
|
expect(typeof webUi.Dropdown).toBe('function');
|
||||||
|
expect(typeof webUi.Form).toBe('function');
|
||||||
|
expect(typeof webUi.InputField).toBe('function');
|
||||||
|
expect(typeof webUi.Label).toBe('function');
|
||||||
|
expect(typeof webUi.SidebarNavItem).toBe('function');
|
||||||
|
expect(typeof webUi.Table).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
74
tests/mocks/mdxeditor.tsx
Normal file
74
tests/mocks/mdxeditor.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, type ReactNode } from 'react';
|
||||||
|
|
||||||
|
export type MDXEditorMethods = {
|
||||||
|
setMarkdown: (value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MDXEditorProps = {
|
||||||
|
markdown: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
readOnly?: boolean;
|
||||||
|
className?: string;
|
||||||
|
contentEditableClassName?: string;
|
||||||
|
plugins?: unknown[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MDXEditor = forwardRef<MDXEditorMethods, MDXEditorProps>(function MDXEditor(
|
||||||
|
{ markdown, onChange, readOnly, className },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
setMarkdown: () => undefined,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return (
|
||||||
|
<div data-testid="md-preview" data-class-name={className}>
|
||||||
|
{markdown}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
aria-label="Markdown Editor"
|
||||||
|
value={markdown}
|
||||||
|
data-class-name={className}
|
||||||
|
onChange={(event) => onChange?.(event.target.value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function Control(): ReactNode {
|
||||||
|
return <span />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function plugin(): Record<string, never> {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockTypeSelect = Control;
|
||||||
|
export const BoldItalicUnderlineToggles = Control;
|
||||||
|
export const CodeToggle = Control;
|
||||||
|
export const CreateLink = Control;
|
||||||
|
export const InsertCodeBlock = Control;
|
||||||
|
export const InsertTable = Control;
|
||||||
|
export const ListsToggle = Control;
|
||||||
|
export const Separator = Control;
|
||||||
|
export const UndoRedo = Control;
|
||||||
|
|
||||||
|
export const codeBlockPlugin = plugin;
|
||||||
|
export const codeMirrorPlugin = plugin;
|
||||||
|
export const headingsPlugin = plugin;
|
||||||
|
export const linkDialogPlugin = plugin;
|
||||||
|
export const linkPlugin = plugin;
|
||||||
|
export const listsPlugin = plugin;
|
||||||
|
export const markdownShortcutPlugin = plugin;
|
||||||
|
export const quotePlugin = plugin;
|
||||||
|
export const tablePlugin = plugin;
|
||||||
|
export const thematicBreakPlugin = plugin;
|
||||||
|
export const toolbarPlugin = plugin;
|
||||||
18
tests/setup.ts
Normal file
18
tests/setup.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Required by React to silence act(...) warnings in jsdom tests.
|
||||||
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
import '@testing-library/jest-dom/vitest';
|
||||||
|
import { cleanup } from '@testing-library/react';
|
||||||
|
import { afterEach, vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('@mdxeditor/editor', () => import('./mocks/mdxeditor'));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
const storage = globalThis.window?.localStorage ?? globalThis.localStorage;
|
||||||
|
if (typeof storage?.clear === 'function') {
|
||||||
|
storage.clear();
|
||||||
|
}
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vitest/config';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
@@ -27,4 +27,25 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./tests/setup.ts'],
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
include: ['src/**/*.{ts,tsx}'],
|
||||||
|
exclude: [
|
||||||
|
'src/**/*.story.{ts,tsx}',
|
||||||
|
'src/**/*.stories.{ts,tsx}',
|
||||||
|
'src/index.ts',
|
||||||
|
'src/styles/**',
|
||||||
|
'src/components/types.ts',
|
||||||
|
'src/types/**',
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
lines: 80,
|
||||||
|
functions: 75,
|
||||||
|
branches: 70,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
582
yarn.lock
582
yarn.lock
@@ -2,6 +2,11 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@acemir/cssom@^0.9.31":
|
||||||
|
version "0.9.31"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@acemir/cssom/-/cssom-0.9.31.tgz#bd5337d290fb8be2ac18391f37386bc53778b0bc"
|
||||||
|
integrity sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==
|
||||||
|
|
||||||
"@adobe/css-tools@^4.4.0":
|
"@adobe/css-tools@^4.4.0":
|
||||||
version "4.4.4"
|
version "4.4.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9"
|
||||||
@@ -12,6 +17,33 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||||
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||||
|
|
||||||
|
"@asamuzakjp/css-color@^5.0.0":
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@asamuzakjp/css-color/-/css-color-5.0.1.tgz#3b9462a9b52f3c6680a0945a3d0851881017550f"
|
||||||
|
integrity sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==
|
||||||
|
dependencies:
|
||||||
|
"@csstools/css-calc" "^3.1.1"
|
||||||
|
"@csstools/css-color-parser" "^4.0.2"
|
||||||
|
"@csstools/css-parser-algorithms" "^4.0.0"
|
||||||
|
"@csstools/css-tokenizer" "^4.0.0"
|
||||||
|
lru-cache "^11.2.6"
|
||||||
|
|
||||||
|
"@asamuzakjp/dom-selector@^6.8.1":
|
||||||
|
version "6.8.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz#39b20993672b106f7cd9a3a9a465212e87e0bfd1"
|
||||||
|
integrity sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==
|
||||||
|
dependencies:
|
||||||
|
"@asamuzakjp/nwsapi" "^2.3.9"
|
||||||
|
bidi-js "^1.0.3"
|
||||||
|
css-tree "^3.1.0"
|
||||||
|
is-potential-custom-element-name "^1.0.1"
|
||||||
|
lru-cache "^11.2.6"
|
||||||
|
|
||||||
|
"@asamuzakjp/nwsapi@^2.3.9":
|
||||||
|
version "2.3.9"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz#ad5549322dfe9d153d4b4dd6f7ff2ae234b06e24"
|
||||||
|
integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==
|
||||||
|
|
||||||
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
|
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
|
||||||
version "7.29.0"
|
version "7.29.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
|
||||||
@@ -175,6 +207,18 @@
|
|||||||
"@babel/helper-string-parser" "^7.27.1"
|
"@babel/helper-string-parser" "^7.27.1"
|
||||||
"@babel/helper-validator-identifier" "^7.28.5"
|
"@babel/helper-validator-identifier" "^7.28.5"
|
||||||
|
|
||||||
|
"@bcoe/v8-coverage@^1.0.2":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa"
|
||||||
|
integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==
|
||||||
|
|
||||||
|
"@bramus/specificity@^2.4.2":
|
||||||
|
version "2.4.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@bramus/specificity/-/specificity-2.4.2.tgz#aa8db8eb173fdee7324f82284833106adeecc648"
|
||||||
|
integrity sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==
|
||||||
|
dependencies:
|
||||||
|
css-tree "^3.0.0"
|
||||||
|
|
||||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.4.0", "@codemirror/autocomplete@^6.7.1":
|
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.3.2", "@codemirror/autocomplete@^6.4.0", "@codemirror/autocomplete@^6.7.1":
|
||||||
version "6.20.0"
|
version "6.20.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz#db818c12dce892a93fb8abadc2426febb002f8c1"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz#db818c12dce892a93fb8abadc2426febb002f8c1"
|
||||||
@@ -460,9 +504,9 @@
|
|||||||
"@codemirror/legacy-modes" "^6.4.0"
|
"@codemirror/legacy-modes" "^6.4.0"
|
||||||
|
|
||||||
"@codemirror/language@^6.0.0", "@codemirror/language@^6.11.3", "@codemirror/language@^6.3.0", "@codemirror/language@^6.3.2", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0", "@codemirror/language@^6.8.0":
|
"@codemirror/language@^6.0.0", "@codemirror/language@^6.11.3", "@codemirror/language@^6.3.0", "@codemirror/language@^6.3.2", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0", "@codemirror/language@^6.8.0":
|
||||||
version "6.12.1"
|
version "6.12.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/language/-/language-6.12.1.tgz#d615f7b099a39248312feaaf0bfafce4418aac1b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@codemirror/language/-/language-6.12.2.tgz#7db5a46757411cf251e8f450474c05710c27d42c"
|
||||||
integrity sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==
|
integrity sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/state" "^6.0.0"
|
"@codemirror/state" "^6.0.0"
|
||||||
"@codemirror/view" "^6.23.0"
|
"@codemirror/view" "^6.23.0"
|
||||||
@@ -569,6 +613,39 @@
|
|||||||
react-devtools-inline "4.4.0"
|
react-devtools-inline "4.4.0"
|
||||||
react-is "^17.0.2"
|
react-is "^17.0.2"
|
||||||
|
|
||||||
|
"@csstools/color-helpers@^6.0.2":
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/color-helpers/-/color-helpers-6.0.2.tgz#82c59fd30649cf0b4d3c82160489748666e6550b"
|
||||||
|
integrity sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==
|
||||||
|
|
||||||
|
"@csstools/css-calc@^3.1.1":
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/css-calc/-/css-calc-3.1.1.tgz#78b494996dac41a02797dcca18ac3b46d25b3fd7"
|
||||||
|
integrity sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==
|
||||||
|
|
||||||
|
"@csstools/css-color-parser@^4.0.2":
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz#c27e03a3770d0352db92d668d6dde427a37859e5"
|
||||||
|
integrity sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==
|
||||||
|
dependencies:
|
||||||
|
"@csstools/color-helpers" "^6.0.2"
|
||||||
|
"@csstools/css-calc" "^3.1.1"
|
||||||
|
|
||||||
|
"@csstools/css-parser-algorithms@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz#e1c65dc09378b42f26a111fca7f7075fc2c26164"
|
||||||
|
integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==
|
||||||
|
|
||||||
|
"@csstools/css-syntax-patches-for-csstree@^1.0.28":
|
||||||
|
version "1.0.28"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz#cd239a16f95c0ed7c6d74315da4e38f2e93bbf19"
|
||||||
|
integrity sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==
|
||||||
|
|
||||||
|
"@csstools/css-tokenizer@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz#798a33950d11226a0ebb6acafa60f5594424967f"
|
||||||
|
integrity sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.27.3":
|
"@esbuild/aix-ppc64@0.27.3":
|
||||||
version "0.27.3"
|
version "0.27.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2"
|
||||||
@@ -752,6 +829,11 @@
|
|||||||
"@eslint/core" "^1.1.0"
|
"@eslint/core" "^1.1.0"
|
||||||
levn "^0.4.1"
|
levn "^0.4.1"
|
||||||
|
|
||||||
|
"@exodus/bytes@^1.11.0", "@exodus/bytes@^1.6.0":
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@exodus/bytes/-/bytes-1.14.1.tgz#9b5c29077162a35f1bd25613e0cd3c239f6e7ad8"
|
||||||
|
integrity sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==
|
||||||
|
|
||||||
"@floating-ui/core@^1.7.4":
|
"@floating-ui/core@^1.7.4":
|
||||||
version "1.7.4"
|
version "1.7.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4"
|
||||||
@@ -850,7 +932,7 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
|
||||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||||
|
|
||||||
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
|
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28", "@jridgewell/trace-mapping@^0.3.31":
|
||||||
version "0.3.31"
|
version "0.3.31"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
|
||||||
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
|
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
|
||||||
@@ -1797,6 +1879,11 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c"
|
||||||
integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==
|
integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==
|
||||||
|
|
||||||
|
"@standard-schema/spec@^1.0.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
|
||||||
|
integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==
|
||||||
|
|
||||||
"@stitches/core@^1.2.6":
|
"@stitches/core@^1.2.6":
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@stitches/core/-/core-1.2.8.tgz#dce3b8fdc764fbc6dbea30c83b73bfb52cf96173"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@stitches/core/-/core-1.2.8.tgz#dce3b8fdc764fbc6dbea30c83b73bfb52cf96173"
|
||||||
@@ -1898,7 +1985,7 @@
|
|||||||
picocolors "1.1.1"
|
picocolors "1.1.1"
|
||||||
pretty-format "^27.0.2"
|
pretty-format "^27.0.2"
|
||||||
|
|
||||||
"@testing-library/jest-dom@^6.6.3":
|
"@testing-library/jest-dom@^6.6.3", "@testing-library/jest-dom@^6.9.1":
|
||||||
version "6.9.1"
|
version "6.9.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz#7613a04e146dd2976d24ddf019730d57a89d56c2"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz#7613a04e146dd2976d24ddf019730d57a89d56c2"
|
||||||
integrity sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==
|
integrity sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==
|
||||||
@@ -1910,6 +1997,13 @@
|
|||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
redent "^3.0.0"
|
redent "^3.0.0"
|
||||||
|
|
||||||
|
"@testing-library/react@^16.3.0":
|
||||||
|
version "16.3.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@testing-library/react/-/react-16.3.2.tgz#672883b7acb8e775fc0492d9e9d25e06e89786d0"
|
||||||
|
integrity sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.5"
|
||||||
|
|
||||||
"@testing-library/user-event@^14.6.1":
|
"@testing-library/user-event@^14.6.1":
|
||||||
version "14.6.1"
|
version "14.6.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149"
|
||||||
@@ -2024,6 +2118,13 @@
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
||||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||||
|
|
||||||
|
"@types/node@^25.3.0":
|
||||||
|
version "25.3.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/node/-/node-25.3.0.tgz#749b1bd4058e51b72e22bd41e9eab6ebd0180470"
|
||||||
|
integrity sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==
|
||||||
|
dependencies:
|
||||||
|
undici-types "~7.18.0"
|
||||||
|
|
||||||
"@types/react-dom@^19.0.0":
|
"@types/react-dom@^19.0.0":
|
||||||
version "19.2.3"
|
version "19.2.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
||||||
@@ -2159,6 +2260,22 @@
|
|||||||
"@types/babel__core" "^7.20.5"
|
"@types/babel__core" "^7.20.5"
|
||||||
react-refresh "^0.18.0"
|
react-refresh "^0.18.0"
|
||||||
|
|
||||||
|
"@vitest/coverage-v8@^4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz#b9c4db7479acd51d5f0ced91b2853c29c3d0cda7"
|
||||||
|
integrity sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==
|
||||||
|
dependencies:
|
||||||
|
"@bcoe/v8-coverage" "^1.0.2"
|
||||||
|
"@vitest/utils" "4.0.18"
|
||||||
|
ast-v8-to-istanbul "^0.3.10"
|
||||||
|
istanbul-lib-coverage "^3.2.2"
|
||||||
|
istanbul-lib-report "^3.0.1"
|
||||||
|
istanbul-reports "^3.2.0"
|
||||||
|
magicast "^0.5.1"
|
||||||
|
obug "^2.1.1"
|
||||||
|
std-env "^3.10.0"
|
||||||
|
tinyrainbow "^3.0.3"
|
||||||
|
|
||||||
"@vitest/expect@3.2.4":
|
"@vitest/expect@3.2.4":
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433"
|
||||||
@@ -2170,6 +2287,27 @@
|
|||||||
chai "^5.2.0"
|
chai "^5.2.0"
|
||||||
tinyrainbow "^2.0.0"
|
tinyrainbow "^2.0.0"
|
||||||
|
|
||||||
|
"@vitest/expect@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/expect/-/expect-4.0.18.tgz#361510d99fbf20eb814222e4afcb8539d79dc94d"
|
||||||
|
integrity sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==
|
||||||
|
dependencies:
|
||||||
|
"@standard-schema/spec" "^1.0.0"
|
||||||
|
"@types/chai" "^5.2.2"
|
||||||
|
"@vitest/spy" "4.0.18"
|
||||||
|
"@vitest/utils" "4.0.18"
|
||||||
|
chai "^6.2.1"
|
||||||
|
tinyrainbow "^3.0.3"
|
||||||
|
|
||||||
|
"@vitest/mocker@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/mocker/-/mocker-4.0.18.tgz#b9735da114ef65ea95652c5bdf13159c6fab4865"
|
||||||
|
integrity sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==
|
||||||
|
dependencies:
|
||||||
|
"@vitest/spy" "4.0.18"
|
||||||
|
estree-walker "^3.0.3"
|
||||||
|
magic-string "^0.30.21"
|
||||||
|
|
||||||
"@vitest/pretty-format@3.2.4":
|
"@vitest/pretty-format@3.2.4":
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4"
|
||||||
@@ -2177,6 +2315,30 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tinyrainbow "^2.0.0"
|
tinyrainbow "^2.0.0"
|
||||||
|
|
||||||
|
"@vitest/pretty-format@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/pretty-format/-/pretty-format-4.0.18.tgz#fbccd4d910774072ec15463553edb8ca5ce53218"
|
||||||
|
integrity sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow "^3.0.3"
|
||||||
|
|
||||||
|
"@vitest/runner@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/runner/-/runner-4.0.18.tgz#c2c0a3ed226ec85e9312f9cc8c43c5b3a893a8b1"
|
||||||
|
integrity sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==
|
||||||
|
dependencies:
|
||||||
|
"@vitest/utils" "4.0.18"
|
||||||
|
pathe "^2.0.3"
|
||||||
|
|
||||||
|
"@vitest/snapshot@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/snapshot/-/snapshot-4.0.18.tgz#bcb40fd6d742679c2ac927ba295b66af1c6c34c5"
|
||||||
|
integrity sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==
|
||||||
|
dependencies:
|
||||||
|
"@vitest/pretty-format" "4.0.18"
|
||||||
|
magic-string "^0.30.21"
|
||||||
|
pathe "^2.0.3"
|
||||||
|
|
||||||
"@vitest/spy@3.2.4":
|
"@vitest/spy@3.2.4":
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599"
|
||||||
@@ -2184,6 +2346,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tinyspy "^4.0.3"
|
tinyspy "^4.0.3"
|
||||||
|
|
||||||
|
"@vitest/spy@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/spy/-/spy-4.0.18.tgz#ba0f20503fb6d08baf3309d690b3efabdfa88762"
|
||||||
|
integrity sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==
|
||||||
|
|
||||||
"@vitest/utils@3.2.4":
|
"@vitest/utils@3.2.4":
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea"
|
||||||
@@ -2193,6 +2360,14 @@
|
|||||||
loupe "^3.1.4"
|
loupe "^3.1.4"
|
||||||
tinyrainbow "^2.0.0"
|
tinyrainbow "^2.0.0"
|
||||||
|
|
||||||
|
"@vitest/utils@4.0.18":
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/@vitest/utils/-/utils-4.0.18.tgz#9636b16d86a4152ec68a8d6859cff702896433d4"
|
||||||
|
integrity sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==
|
||||||
|
dependencies:
|
||||||
|
"@vitest/pretty-format" "4.0.18"
|
||||||
|
tinyrainbow "^3.0.3"
|
||||||
|
|
||||||
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
|
acorn-jsx@^5.0.0, acorn-jsx@^5.3.2:
|
||||||
version "5.3.2"
|
version "5.3.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||||
@@ -2203,6 +2378,11 @@ acorn@^8.0.0, acorn@^8.15.0, acorn@^8.16.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
|
||||||
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
||||||
|
|
||||||
|
agent-base@^7.1.0, agent-base@^7.1.2:
|
||||||
|
version "7.1.4"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
|
||||||
|
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
|
||||||
|
|
||||||
ajv@^6.12.4:
|
ajv@^6.12.4:
|
||||||
version "6.14.0"
|
version "6.14.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
||||||
@@ -2282,6 +2462,15 @@ ast-types@^0.16.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.1"
|
tslib "^2.0.1"
|
||||||
|
|
||||||
|
ast-v8-to-istanbul@^0.3.10:
|
||||||
|
version "0.3.11"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz#725b1f5e2ffdc8d71620cb5e78d6dc976d65e97a"
|
||||||
|
integrity sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.31"
|
||||||
|
estree-walker "^3.0.3"
|
||||||
|
js-tokens "^10.0.0"
|
||||||
|
|
||||||
axe-core@^4.2.0:
|
axe-core@^4.2.0:
|
||||||
version "4.11.1"
|
version "4.11.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/axe-core/-/axe-core-4.11.1.tgz#052ff9b2cbf543f5595028b583e4763b40c78ea7"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/axe-core/-/axe-core-4.11.1.tgz#052ff9b2cbf543f5595028b583e4763b40c78ea7"
|
||||||
@@ -2302,6 +2491,13 @@ baseline-browser-mapping@^2.9.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz#5b09935025bf8a80e29130251e337c6a7fc8cbb9"
|
||||||
integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==
|
integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==
|
||||||
|
|
||||||
|
bidi-js@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2"
|
||||||
|
integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==
|
||||||
|
dependencies:
|
||||||
|
require-from-string "^2.0.2"
|
||||||
|
|
||||||
binary-extensions@^2.0.0:
|
binary-extensions@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
@@ -2353,9 +2549,9 @@ camelcase-css@^2.0.1:
|
|||||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001759:
|
caniuse-lite@^1.0.30001759:
|
||||||
version "1.0.30001772"
|
version "1.0.30001774"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz#aa8a176eba0006e78c965a8215c7a1ceb030122d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz#0e576b6f374063abcd499d202b9ba1301be29b70"
|
||||||
integrity sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==
|
integrity sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==
|
||||||
|
|
||||||
ccount@^2.0.0:
|
ccount@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
@@ -2373,6 +2569,11 @@ chai@^5.2.0:
|
|||||||
loupe "^3.1.0"
|
loupe "^3.1.0"
|
||||||
pathval "^2.0.0"
|
pathval "^2.0.0"
|
||||||
|
|
||||||
|
chai@^6.2.1:
|
||||||
|
version "6.2.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e"
|
||||||
|
integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==
|
||||||
|
|
||||||
character-entities-html4@^2.0.0:
|
character-entities-html4@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
|
||||||
@@ -2475,6 +2676,14 @@ cross-spawn@^7.0.6:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
css-tree@^3.0.0, css-tree@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/css-tree/-/css-tree-3.1.0.tgz#7aabc035f4e66b5c86f54570d55e05b1346eb0fd"
|
||||||
|
integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==
|
||||||
|
dependencies:
|
||||||
|
mdn-data "2.12.2"
|
||||||
|
source-map-js "^1.0.1"
|
||||||
|
|
||||||
css.escape@^1.5.1:
|
css.escape@^1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||||
@@ -2485,6 +2694,16 @@ cssesc@^3.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||||
|
|
||||||
|
cssstyle@^6.0.1:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/cssstyle/-/cssstyle-6.1.0.tgz#07ae112dd2fbc590d11f6e11c73699bd50f27d51"
|
||||||
|
integrity sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==
|
||||||
|
dependencies:
|
||||||
|
"@asamuzakjp/css-color" "^5.0.0"
|
||||||
|
"@csstools/css-syntax-patches-for-csstree" "^1.0.28"
|
||||||
|
css-tree "^3.1.0"
|
||||||
|
lru-cache "^11.2.6"
|
||||||
|
|
||||||
csstype@^3.2.2:
|
csstype@^3.2.2:
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a"
|
||||||
@@ -2498,13 +2717,26 @@ d@1, d@^1.0.1, d@^1.0.2:
|
|||||||
es5-ext "^0.10.64"
|
es5-ext "^0.10.64"
|
||||||
type "^2.7.2"
|
type "^2.7.2"
|
||||||
|
|
||||||
debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3:
|
data-urls@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/data-urls/-/data-urls-7.0.0.tgz#6dce8b63226a1ecfdd907ce18a8ccfb1eee506d3"
|
||||||
|
integrity sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==
|
||||||
|
dependencies:
|
||||||
|
whatwg-mimetype "^5.0.0"
|
||||||
|
whatwg-url "^16.0.0"
|
||||||
|
|
||||||
|
debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.3:
|
||||||
version "4.4.3"
|
version "4.4.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.3"
|
ms "^2.1.3"
|
||||||
|
|
||||||
|
decimal.js@^10.6.0:
|
||||||
|
version "10.6.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a"
|
||||||
|
integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==
|
||||||
|
|
||||||
decode-named-character-reference@^1.0.0:
|
decode-named-character-reference@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f"
|
||||||
@@ -2615,6 +2847,16 @@ empathic@^2.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/empathic/-/empathic-2.0.0.tgz#71d3c2b94fad49532ef98a6c34be0386659f6131"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/empathic/-/empathic-2.0.0.tgz#71d3c2b94fad49532ef98a6c34be0386659f6131"
|
||||||
integrity sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==
|
integrity sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==
|
||||||
|
|
||||||
|
entities@^6.0.0:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
|
||||||
|
integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
|
||||||
|
|
||||||
|
es-module-lexer@^1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a"
|
||||||
|
integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
|
||||||
|
|
||||||
es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14:
|
es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14:
|
||||||
version "0.10.64"
|
version "0.10.64"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
|
||||||
@@ -2694,10 +2936,10 @@ escape-string-regexp@^5.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
|
||||||
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^7.0.1:
|
eslint-plugin-react-hooks@^7.1.0-canary-ab18f33d-20260220:
|
||||||
version "7.0.1"
|
version "7.1.0-canary-fd524fe0-20251121"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.0-canary-fd524fe0-20251121.tgz#976c8feb747505e7eae45e15c5f0f16df4553ef9"
|
||||||
integrity sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==
|
integrity sha512-G5we0+XjZTKpjkLL9AgdWxzmo4mqelVDIYzoR1dBlhhiN8Lf5PQ+l8frr+BmX02nU4g0AEez3eGSF/LNfHokEw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.24.4"
|
"@babel/core" "^7.24.4"
|
||||||
"@babel/parser" "^7.24.4"
|
"@babel/parser" "^7.24.4"
|
||||||
@@ -2827,6 +3069,13 @@ estree-walker@^2.0.2:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||||
|
|
||||||
|
estree-walker@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
|
||||||
|
integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "^1.0.0"
|
||||||
|
|
||||||
esutils@^2.0.2:
|
esutils@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
@@ -2840,6 +3089,11 @@ event-emitter@^0.3.5:
|
|||||||
d "1"
|
d "1"
|
||||||
es5-ext "~0.10.14"
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
|
expect-type@^1.2.2:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68"
|
||||||
|
integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==
|
||||||
|
|
||||||
ext@^1.7.0:
|
ext@^1.7.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
|
||||||
@@ -2980,6 +3234,11 @@ globals@^17.3.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/globals/-/globals-17.3.0.tgz#8b96544c2fa91afada02747cc9731c002a96f3b9"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/globals/-/globals-17.3.0.tgz#8b96544c2fa91afada02747cc9731c002a96f3b9"
|
||||||
integrity sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==
|
integrity sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==
|
||||||
|
|
||||||
|
has-flag@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
hasown@^2.0.2:
|
hasown@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||||
@@ -2999,6 +3258,34 @@ hermes-parser@^0.25.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
hermes-estree "0.25.1"
|
hermes-estree "0.25.1"
|
||||||
|
|
||||||
|
html-encoding-sniffer@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz#f8d9390b3b348b50d4f61c16dd2ef5c05980a882"
|
||||||
|
integrity sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==
|
||||||
|
dependencies:
|
||||||
|
"@exodus/bytes" "^1.6.0"
|
||||||
|
|
||||||
|
html-escaper@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
|
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||||
|
|
||||||
|
http-proxy-agent@^7.0.2:
|
||||||
|
version "7.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
|
||||||
|
integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
|
||||||
|
dependencies:
|
||||||
|
agent-base "^7.1.0"
|
||||||
|
debug "^4.3.4"
|
||||||
|
|
||||||
|
https-proxy-agent@^7.0.6:
|
||||||
|
version "7.0.6"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
|
||||||
|
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
|
||||||
|
dependencies:
|
||||||
|
agent-base "^7.1.2"
|
||||||
|
debug "4"
|
||||||
|
|
||||||
ieee754@^1.2.1:
|
ieee754@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
@@ -3095,6 +3382,11 @@ is-number@^7.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||||
|
|
||||||
|
is-potential-custom-element-name@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
|
||||||
|
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
|
||||||
|
|
||||||
is-wsl@^3.1.0:
|
is-wsl@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-wsl/-/is-wsl-3.1.1.tgz#327897b26832a3eb117da6c27492d04ca132594f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-wsl/-/is-wsl-3.1.1.tgz#327897b26832a3eb117da6c27492d04ca132594f"
|
||||||
@@ -3112,11 +3404,38 @@ isomorphic.js@^0.2.4:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88"
|
||||||
integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==
|
integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==
|
||||||
|
|
||||||
|
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
|
||||||
|
integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
|
||||||
|
|
||||||
|
istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
|
||||||
|
integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
|
||||||
|
dependencies:
|
||||||
|
istanbul-lib-coverage "^3.0.0"
|
||||||
|
make-dir "^4.0.0"
|
||||||
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
istanbul-reports@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93"
|
||||||
|
integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==
|
||||||
|
dependencies:
|
||||||
|
html-escaper "^2.0.0"
|
||||||
|
istanbul-lib-report "^3.0.0"
|
||||||
|
|
||||||
jiti@^1.21.7:
|
jiti@^1.21.7:
|
||||||
version "1.21.7"
|
version "1.21.7"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9"
|
||||||
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
|
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
|
||||||
|
|
||||||
|
js-tokens@^10.0.0:
|
||||||
|
version "10.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831"
|
||||||
|
integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
@@ -3129,6 +3448,33 @@ js-yaml@4.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse "^2.0.1"
|
argparse "^2.0.1"
|
||||||
|
|
||||||
|
jsdom@^28.1.0:
|
||||||
|
version "28.1.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/jsdom/-/jsdom-28.1.0.tgz#ac4203e58fd24d7b0f34359ab00d6d9caebd4b62"
|
||||||
|
integrity sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==
|
||||||
|
dependencies:
|
||||||
|
"@acemir/cssom" "^0.9.31"
|
||||||
|
"@asamuzakjp/dom-selector" "^6.8.1"
|
||||||
|
"@bramus/specificity" "^2.4.2"
|
||||||
|
"@exodus/bytes" "^1.11.0"
|
||||||
|
cssstyle "^6.0.1"
|
||||||
|
data-urls "^7.0.0"
|
||||||
|
decimal.js "^10.6.0"
|
||||||
|
html-encoding-sniffer "^6.0.0"
|
||||||
|
http-proxy-agent "^7.0.2"
|
||||||
|
https-proxy-agent "^7.0.6"
|
||||||
|
is-potential-custom-element-name "^1.0.1"
|
||||||
|
parse5 "^8.0.0"
|
||||||
|
saxes "^6.0.0"
|
||||||
|
symbol-tree "^3.2.4"
|
||||||
|
tough-cookie "^6.0.0"
|
||||||
|
undici "^7.21.0"
|
||||||
|
w3c-xmlserializer "^5.0.0"
|
||||||
|
webidl-conversions "^8.0.1"
|
||||||
|
whatwg-mimetype "^5.0.0"
|
||||||
|
whatwg-url "^16.0.0"
|
||||||
|
xml-name-validator "^5.0.0"
|
||||||
|
|
||||||
jsesc@^3.0.2:
|
jsesc@^3.0.2:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
|
||||||
@@ -3220,7 +3566,7 @@ loupe@^3.1.0, loupe@^3.1.4:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76"
|
||||||
integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==
|
integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==
|
||||||
|
|
||||||
lru-cache@^11.0.0:
|
lru-cache@^11.0.0, lru-cache@^11.2.6:
|
||||||
version "11.2.6"
|
version "11.2.6"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58"
|
||||||
integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==
|
integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==
|
||||||
@@ -3237,13 +3583,29 @@ lz-string@^1.4.4, lz-string@^1.5.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
magic-string@^0.30.0:
|
magic-string@^0.30.0, magic-string@^0.30.21:
|
||||||
version "0.30.21"
|
version "0.30.21"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
||||||
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/sourcemap-codec" "^1.5.5"
|
"@jridgewell/sourcemap-codec" "^1.5.5"
|
||||||
|
|
||||||
|
magicast@^0.5.1:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/magicast/-/magicast-0.5.2.tgz#70cea9df729c164485049ea5df85a390281dfb9d"
|
||||||
|
integrity sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.29.0"
|
||||||
|
"@babel/types" "^7.29.0"
|
||||||
|
source-map-js "^1.2.1"
|
||||||
|
|
||||||
|
make-dir@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
|
||||||
|
integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
|
||||||
|
dependencies:
|
||||||
|
semver "^7.5.3"
|
||||||
|
|
||||||
markdown-table@^3.0.0:
|
markdown-table@^3.0.0:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
|
||||||
@@ -3414,6 +3776,11 @@ mdast-util-to-string@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/mdast" "^4.0.0"
|
"@types/mdast" "^4.0.0"
|
||||||
|
|
||||||
|
mdn-data@2.12.2:
|
||||||
|
version "2.12.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/mdn-data/-/mdn-data-2.12.2.tgz#9ae6c41a9e65adf61318b32bff7b64fbfb13f8cf"
|
||||||
|
integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==
|
||||||
|
|
||||||
merge2@^1.3.0:
|
merge2@^1.3.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
@@ -3872,6 +4239,11 @@ object-hash@^3.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||||
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
||||||
|
|
||||||
|
obug@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/obug/-/obug-2.1.1.tgz#2cba74ff241beb77d63055ddf4cd1e9f90b538be"
|
||||||
|
integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==
|
||||||
|
|
||||||
open@^10.2.0:
|
open@^10.2.0:
|
||||||
version "10.2.0"
|
version "10.2.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/open/-/open-10.2.0.tgz#b9d855be007620e80b6fb05fac98141fe62db73c"
|
||||||
@@ -3931,6 +4303,13 @@ parse-entities@^4.0.0:
|
|||||||
is-decimal "^2.0.0"
|
is-decimal "^2.0.0"
|
||||||
is-hexadecimal "^2.0.0"
|
is-hexadecimal "^2.0.0"
|
||||||
|
|
||||||
|
parse5@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/parse5/-/parse5-8.0.0.tgz#aceb267f6b15f9b6e6ba9e35bfdd481fc2167b12"
|
||||||
|
integrity sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==
|
||||||
|
dependencies:
|
||||||
|
entities "^6.0.0"
|
||||||
|
|
||||||
path-exists@^4.0.0:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
@@ -3954,6 +4333,11 @@ path-scurry@^2.0.2:
|
|||||||
lru-cache "^11.0.0"
|
lru-cache "^11.0.0"
|
||||||
minipass "^7.1.2"
|
minipass "^7.1.2"
|
||||||
|
|
||||||
|
pathe@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
|
||||||
|
integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
|
||||||
|
|
||||||
pathval@^2.0.0:
|
pathval@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d"
|
||||||
@@ -4069,7 +4453,7 @@ prop-types@^15.7.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.13.1"
|
react-is "^16.13.1"
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0, punycode@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||||
@@ -4221,6 +4605,11 @@ redent@^3.0.0:
|
|||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
|
require-from-string@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||||
|
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||||
|
|
||||||
resolve@^1.1.7, resolve@^1.22.1, resolve@^1.22.8:
|
resolve@^1.1.7, resolve@^1.22.1, resolve@^1.22.8:
|
||||||
version "1.22.11"
|
version "1.22.11"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
|
||||||
@@ -4288,6 +4677,13 @@ sade@^1.7.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mri "^1.1.0"
|
mri "^1.1.0"
|
||||||
|
|
||||||
|
saxes@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
|
||||||
|
integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
|
||||||
|
dependencies:
|
||||||
|
xmlchars "^2.2.0"
|
||||||
|
|
||||||
scheduler@^0.27.0:
|
scheduler@^0.27.0:
|
||||||
version "0.27.0"
|
version "0.27.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
||||||
@@ -4298,7 +4694,7 @@ semver@^6.3.1:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||||
|
|
||||||
semver@^7.7.3:
|
semver@^7.5.3, semver@^7.7.3:
|
||||||
version "7.7.4"
|
version "7.7.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
|
||||||
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
|
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
|
||||||
@@ -4320,7 +4716,12 @@ shebang-regex@^3.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
source-map-js@^1.2.1:
|
siginfo@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
|
||||||
|
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
|
||||||
|
|
||||||
|
source-map-js@^1.0.1, source-map-js@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
@@ -4330,6 +4731,11 @@ source-map@~0.6.1:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b"
|
||||||
|
integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==
|
||||||
|
|
||||||
static-browser-server@1.0.3:
|
static-browser-server@1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/static-browser-server/-/static-browser-server-1.0.3.tgz#9030d141b99ed92c8eec1a7546b87548fd036f5d"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/static-browser-server/-/static-browser-server-1.0.3.tgz#9030d141b99ed92c8eec1a7546b87548fd036f5d"
|
||||||
@@ -4340,6 +4746,11 @@ static-browser-server@1.0.3:
|
|||||||
mime-db "^1.52.0"
|
mime-db "^1.52.0"
|
||||||
outvariant "^1.3.0"
|
outvariant "^1.3.0"
|
||||||
|
|
||||||
|
std-env@^3.10.0:
|
||||||
|
version "3.10.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b"
|
||||||
|
integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==
|
||||||
|
|
||||||
storybook@^10.2.10:
|
storybook@^10.2.10:
|
||||||
version "10.2.10"
|
version "10.2.10"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/storybook/-/storybook-10.2.10.tgz#b0a008c239690889fc52ae95a35305d4c64b4306"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/storybook/-/storybook-10.2.10.tgz#b0a008c239690889fc52ae95a35305d4c64b4306"
|
||||||
@@ -4406,11 +4817,23 @@ sucrase@^3.35.0:
|
|||||||
tinyglobby "^0.2.11"
|
tinyglobby "^0.2.11"
|
||||||
ts-interface-checker "^0.1.9"
|
ts-interface-checker "^0.1.9"
|
||||||
|
|
||||||
|
supports-color@^7.1.0:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||||
|
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||||
|
dependencies:
|
||||||
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
supports-preserve-symlinks-flag@^1.0.0:
|
supports-preserve-symlinks-flag@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
|
symbol-tree@^3.2.4:
|
||||||
|
version "3.2.4"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||||
|
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
|
||||||
|
|
||||||
tabbable@^6.0.0:
|
tabbable@^6.0.0:
|
||||||
version "6.4.0"
|
version "6.4.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581"
|
||||||
@@ -4463,6 +4886,16 @@ tiny-invariant@^1.3.3:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||||
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||||
|
|
||||||
|
tinybench@^2.9.0:
|
||||||
|
version "2.9.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||||
|
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||||
|
|
||||||
|
tinyexec@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251"
|
||||||
|
integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==
|
||||||
|
|
||||||
tinyglobby@^0.2.11, tinyglobby@^0.2.15:
|
tinyglobby@^0.2.11, tinyglobby@^0.2.15:
|
||||||
version "0.2.15"
|
version "0.2.15"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
|
||||||
@@ -4476,11 +4909,28 @@ tinyrainbow@^2.0.0:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294"
|
||||||
integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==
|
integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==
|
||||||
|
|
||||||
|
tinyrainbow@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42"
|
||||||
|
integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==
|
||||||
|
|
||||||
tinyspy@^4.0.3:
|
tinyspy@^4.0.3:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyspy/-/tinyspy-4.0.4.tgz#d77a002fb53a88aa1429b419c1c92492e0c81f78"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyspy/-/tinyspy-4.0.4.tgz#d77a002fb53a88aa1429b419c1c92492e0c81f78"
|
||||||
integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==
|
integrity sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==
|
||||||
|
|
||||||
|
tldts-core@^7.0.23:
|
||||||
|
version "7.0.23"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tldts-core/-/tldts-core-7.0.23.tgz#47bf18282a44641304a399d247703413b5d3e309"
|
||||||
|
integrity sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==
|
||||||
|
|
||||||
|
tldts@^7.0.5:
|
||||||
|
version "7.0.23"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tldts/-/tldts-7.0.23.tgz#444f0f0720fa777839a23ea665e04f61ee57217a"
|
||||||
|
integrity sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==
|
||||||
|
dependencies:
|
||||||
|
tldts-core "^7.0.23"
|
||||||
|
|
||||||
to-regex-range@^5.0.1:
|
to-regex-range@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||||
@@ -4488,6 +4938,20 @@ to-regex-range@^5.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
tough-cookie@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tough-cookie/-/tough-cookie-6.0.0.tgz#11e418b7864a2c0d874702bc8ce0f011261940e5"
|
||||||
|
integrity sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==
|
||||||
|
dependencies:
|
||||||
|
tldts "^7.0.5"
|
||||||
|
|
||||||
|
tr46@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/tr46/-/tr46-6.0.0.tgz#f5a1ae546a0adb32a277a2278d0d17fa2f9093e6"
|
||||||
|
integrity sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.3.1"
|
||||||
|
|
||||||
ts-api-utils@^2.4.0:
|
ts-api-utils@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8"
|
||||||
@@ -4544,6 +5008,16 @@ typescript@^5.6.2:
|
|||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||||
|
|
||||||
|
undici-types@~7.18.0:
|
||||||
|
version "7.18.2"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9"
|
||||||
|
integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==
|
||||||
|
|
||||||
|
undici@^7.21.0:
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/undici/-/undici-7.22.0.tgz#7a82590a5908e504a47d85c60b0f89ca14240e60"
|
||||||
|
integrity sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==
|
||||||
|
|
||||||
unidiff@^1.0.2:
|
unidiff@^1.0.2:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/unidiff/-/unidiff-1.0.4.tgz#45096a898285821c51e22e84be4215c05d6511cd"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/unidiff/-/unidiff-1.0.4.tgz#45096a898285821c51e22e84be4215c05d6511cd"
|
||||||
@@ -4657,7 +5131,7 @@ vfile-message@^4.0.0:
|
|||||||
"@types/unist" "^3.0.0"
|
"@types/unist" "^3.0.0"
|
||||||
unist-util-stringify-position "^4.0.0"
|
unist-util-stringify-position "^4.0.0"
|
||||||
|
|
||||||
vite@^7.0.0:
|
"vite@^6.0.0 || ^7.0.0", vite@^7.0.0:
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507"
|
||||||
integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==
|
integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==
|
||||||
@@ -4671,16 +5145,68 @@ vite@^7.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.3"
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
|
vitest@^4.0.18:
|
||||||
|
version "4.0.18"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/vitest/-/vitest-4.0.18.tgz#56f966353eca0b50f4df7540cd4350ca6d454a05"
|
||||||
|
integrity sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==
|
||||||
|
dependencies:
|
||||||
|
"@vitest/expect" "4.0.18"
|
||||||
|
"@vitest/mocker" "4.0.18"
|
||||||
|
"@vitest/pretty-format" "4.0.18"
|
||||||
|
"@vitest/runner" "4.0.18"
|
||||||
|
"@vitest/snapshot" "4.0.18"
|
||||||
|
"@vitest/spy" "4.0.18"
|
||||||
|
"@vitest/utils" "4.0.18"
|
||||||
|
es-module-lexer "^1.7.0"
|
||||||
|
expect-type "^1.2.2"
|
||||||
|
magic-string "^0.30.21"
|
||||||
|
obug "^2.1.1"
|
||||||
|
pathe "^2.0.3"
|
||||||
|
picomatch "^4.0.3"
|
||||||
|
std-env "^3.10.0"
|
||||||
|
tinybench "^2.9.0"
|
||||||
|
tinyexec "^1.0.2"
|
||||||
|
tinyglobby "^0.2.15"
|
||||||
|
tinyrainbow "^3.0.3"
|
||||||
|
vite "^6.0.0 || ^7.0.0"
|
||||||
|
why-is-node-running "^2.3.0"
|
||||||
|
|
||||||
w3c-keyname@^2.2.4:
|
w3c-keyname@^2.2.4:
|
||||||
version "2.2.8"
|
version "2.2.8"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||||
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
|
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
|
||||||
|
|
||||||
|
w3c-xmlserializer@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"
|
||||||
|
integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==
|
||||||
|
dependencies:
|
||||||
|
xml-name-validator "^5.0.0"
|
||||||
|
|
||||||
|
webidl-conversions@^8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/webidl-conversions/-/webidl-conversions-8.0.1.tgz#0657e571fe6f06fcb15ca50ed1fdbcb495cd1686"
|
||||||
|
integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==
|
||||||
|
|
||||||
webpack-virtual-modules@^0.6.2:
|
webpack-virtual-modules@^0.6.2:
|
||||||
version "0.6.2"
|
version "0.6.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
|
||||||
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
||||||
|
|
||||||
|
whatwg-mimetype@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz#d8232895dbd527ceaee74efd4162008fb8a8cf48"
|
||||||
|
integrity sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==
|
||||||
|
|
||||||
|
whatwg-url@^16.0.0:
|
||||||
|
version "16.0.1"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/whatwg-url/-/whatwg-url-16.0.1.tgz#047f7f4bd36ef76b7198c172d1b1cebc66f764dd"
|
||||||
|
integrity sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==
|
||||||
|
dependencies:
|
||||||
|
"@exodus/bytes" "^1.11.0"
|
||||||
|
tr46 "^6.0.0"
|
||||||
|
webidl-conversions "^8.0.1"
|
||||||
|
|
||||||
which@^2.0.1:
|
which@^2.0.1:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||||
@@ -4688,6 +5214,14 @@ which@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
why-is-node-running@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04"
|
||||||
|
integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==
|
||||||
|
dependencies:
|
||||||
|
siginfo "^2.0.0"
|
||||||
|
stackback "0.0.2"
|
||||||
|
|
||||||
word-wrap@^1.2.5:
|
word-wrap@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||||
@@ -4705,6 +5239,16 @@ wsl-utils@^0.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-wsl "^3.1.0"
|
is-wsl "^3.1.0"
|
||||||
|
|
||||||
|
xml-name-validator@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673"
|
||||||
|
integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==
|
||||||
|
|
||||||
|
xmlchars@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||||
|
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||||
|
|
||||||
yallist@^3.0.2:
|
yallist@^3.0.2:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
resolved "https://nexus.beatrice.wtf/repository/npm-group/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||||
|
|||||||
Reference in New Issue
Block a user