Compare commits
8 Commits
6bc156284b
...
renovate/e
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f10a04de0 | |||
| 1523f7be2c | |||
| b664c99944 | |||
| 3d4a4a5f57 | |||
| f9864842b5 | |||
| dd084369e9 | |||
| 850eed0766 | |||
| 4904bea29c |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@panic/web-ui",
|
||||
"version": "0.1.17",
|
||||
"version": "0.1.18",
|
||||
"license": "AGPL-3.0-only",
|
||||
"description": "Core components for panic.haus web applications",
|
||||
"type": "module",
|
||||
@@ -84,11 +84,14 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": "^7.0.0",
|
||||
"storybook": "^10.2.10",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"vite": "^7.0.0",
|
||||
"vitest": "^4.0.18",
|
||||
"yjs": "^13.6.24"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^25.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const meta = {
|
||||
docs: {
|
||||
description: {
|
||||
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' } },
|
||||
},
|
||||
type: {
|
||||
description: 'Native date input type.',
|
||||
options: ['date', 'datetime-local', 'time'],
|
||||
description: 'DatePicker mode.',
|
||||
options: ['date', 'date-time', 'time'],
|
||||
control: 'inline-radio',
|
||||
table: { type: { summary: "'date' | 'datetime-local' | 'time'" } },
|
||||
table: { type: { summary: "'date' | 'date-time' | 'time'" } },
|
||||
},
|
||||
size: {
|
||||
description: 'Input size.',
|
||||
@@ -55,6 +55,22 @@ const meta = {
|
||||
control: 'text',
|
||||
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: {
|
||||
description: 'Native input `name` attribute.',
|
||||
control: 'text',
|
||||
@@ -108,7 +124,7 @@ const meta = {
|
||||
},
|
||||
args: {
|
||||
label: 'Schedule at',
|
||||
type: 'datetime-local',
|
||||
type: 'date-time',
|
||||
value: '',
|
||||
size: 'md',
|
||||
width: 'md',
|
||||
@@ -124,8 +140,8 @@ export const DateOnly: Story = {
|
||||
type: 'date',
|
||||
label: 'Publish date',
|
||||
},
|
||||
render: (args) => {
|
||||
const [value, setValue] = useState('2031-05-20');
|
||||
render: function DateOnlyRender(args) {
|
||||
const [value, setValue] = useState('2031/05/20');
|
||||
return (
|
||||
<DatePicker
|
||||
{...args}
|
||||
@@ -141,11 +157,11 @@ export const DateOnly: Story = {
|
||||
|
||||
export const DateTime: Story = {
|
||||
args: {
|
||||
type: 'datetime-local',
|
||||
type: 'date-time',
|
||||
label: 'Schedule at',
|
||||
},
|
||||
render: (args) => {
|
||||
const [value, setValue] = useState('2031-05-20T14:30');
|
||||
render: function DateTimeRender(args) {
|
||||
const [value, setValue] = useState('2031/05/20 14:30');
|
||||
return (
|
||||
<DatePicker
|
||||
{...args}
|
||||
@@ -167,7 +183,7 @@ export const TimeOnlyInline: Story = {
|
||||
size: 'sm',
|
||||
rightIcon: <CalendarDaysIcon className="h-4 w-4 ui-body-secondary" />,
|
||||
},
|
||||
render: (args) => {
|
||||
render: function TimeOnlyInlineRender(args) {
|
||||
const [value, setValue] = useState('09:00');
|
||||
return (
|
||||
<DatePicker
|
||||
@@ -182,9 +198,10 @@ export const TimeOnlyInline: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
export const ErrorState: Story = {
|
||||
name: 'Error',
|
||||
args: {
|
||||
type: 'datetime-local',
|
||||
type: 'date-time',
|
||||
label: 'Schedule at',
|
||||
value: '',
|
||||
error: 'Pick a valid future date and time',
|
||||
@@ -193,20 +210,20 @@ export const Error: Story = {
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
type: 'datetime-local',
|
||||
type: 'date-time',
|
||||
label: 'Published at',
|
||||
value: '2031-05-20T14:30',
|
||||
value: '2031/05/20 14:30',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const SizeMatrix: Story = {
|
||||
args: {
|
||||
type: 'datetime-local',
|
||||
type: 'date-time',
|
||||
label: 'Schedule at',
|
||||
},
|
||||
render: (args) => {
|
||||
const [value, setValue] = useState('2031-05-20T14:30');
|
||||
render: function SizeMatrixRender(args) {
|
||||
const [value, setValue] = useState('2031/05/20 14:30');
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<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>;
|
||||
|
||||
export const Stacked: Story = {
|
||||
render: (args) => {
|
||||
render: function StackedRender(args) {
|
||||
const [value, setValue] = useState(args.value);
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -119,7 +119,7 @@ export const Inline: Story = {
|
||||
layout: 'inline',
|
||||
size: 'sm',
|
||||
},
|
||||
render: (args) => {
|
||||
render: function InlineRender(args) {
|
||||
const [value, setValue] = useState(args.value);
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -147,7 +147,7 @@ export const WithError: Story = {
|
||||
};
|
||||
|
||||
export const SizeMatrix: Story = {
|
||||
render: (args) => {
|
||||
render: function SizeMatrixRender(args) {
|
||||
const [value, setValue] = useState(args.value);
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Basic: Story = {
|
||||
};
|
||||
|
||||
export const WithActions: Story = {
|
||||
render: (args) => {
|
||||
render: function WithActionsRender(args) {
|
||||
const [title, setTitle] = useState('Storybook powered CMS');
|
||||
const [status, setStatus] = useState('draft');
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ export const Text: Story = {
|
||||
label: 'Title',
|
||||
placeholder: 'Write a title',
|
||||
},
|
||||
render: (args) => {
|
||||
render: function TextRender(args) {
|
||||
const [value, setValue] = useState('Storybook integration');
|
||||
return (
|
||||
<InputField
|
||||
@@ -148,7 +148,7 @@ export const PasswordWithToggle: Story = {
|
||||
label: 'Password',
|
||||
placeholder: 'Type a strong password',
|
||||
},
|
||||
render: (args) => {
|
||||
render: function PasswordWithToggleRender(args) {
|
||||
const [value, setValue] = useState('pa55word');
|
||||
return (
|
||||
<InputField
|
||||
@@ -171,7 +171,7 @@ export const InlineWithIcon: Story = {
|
||||
size: 'sm',
|
||||
rightIcon: <MagnifyingGlassIcon className="h-4 w-4 ui-body-secondary" />,
|
||||
},
|
||||
render: (args) => {
|
||||
render: function InlineWithIconRender(args) {
|
||||
const [value, setValue] = useState('posts');
|
||||
return (
|
||||
<InputField
|
||||
@@ -186,7 +186,8 @@ export const InlineWithIcon: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
export const ErrorState: Story = {
|
||||
name: 'Error',
|
||||
args: {
|
||||
type: 'email',
|
||||
label: 'Email',
|
||||
@@ -210,7 +211,7 @@ export const SizeMatrix: Story = {
|
||||
label: 'Name',
|
||||
placeholder: 'Enter value',
|
||||
},
|
||||
render: (args) => {
|
||||
render: function SizeMatrixRender(args) {
|
||||
const [value, setValue] = useState('Beatrice');
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
|
||||
@@ -53,7 +53,8 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Body: Story = {};
|
||||
|
||||
export const Error: Story = {
|
||||
export const ErrorState: Story = {
|
||||
name: 'Error',
|
||||
args: {
|
||||
variant: 'error',
|
||||
children: 'This field is required',
|
||||
|
||||
@@ -115,7 +115,7 @@ export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Editable: Story = {
|
||||
render: (args) => {
|
||||
render: function EditableRender(args) {
|
||||
const [markdown, setMarkdown] = useState(args.markdown);
|
||||
return (
|
||||
<div className="w-full max-w-2xl">
|
||||
|
||||
@@ -183,7 +183,7 @@ export const Empty: Story = {
|
||||
};
|
||||
|
||||
export const InteractiveSortingAndPagination: Story = {
|
||||
render: () => {
|
||||
render: function InteractiveSortingAndPaginationRender() {
|
||||
const [sorting, setSorting] = useState<SortState | null>({
|
||||
field: 'name',
|
||||
direction: 'asc',
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
--bg-page: #16121a;
|
||||
--surface-bg: rgba(24, 24, 27, 0.45);
|
||||
--surface-bg-strong: rgba(24, 24, 27, 0.62);
|
||||
--datepicker-menu-bg: #18181b;
|
||||
--surface-border: rgba(82, 82, 91, 0.6);
|
||||
--surface-divider: rgba(63, 63, 70, 0.85);
|
||||
--text-primary: #d5cfdf;
|
||||
@@ -59,6 +60,7 @@
|
||||
--bg-page: #f7f7fb;
|
||||
--surface-bg: rgba(255, 255, 255, 0.9);
|
||||
--surface-bg-strong: rgba(255, 255, 255, 0.98);
|
||||
--datepicker-menu-bg: #ffffff;
|
||||
--surface-border: rgba(161, 161, 170, 0.45);
|
||||
--surface-divider: rgba(212, 212, 216, 0.9);
|
||||
--text-primary: #52485c;
|
||||
|
||||
@@ -327,3 +327,190 @@
|
||||
color: var(--text-secondary);
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ describe('Button', () => {
|
||||
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" />);
|
||||
|
||||
|
||||
@@ -51,4 +51,12 @@ describe('Chip', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,41 +1,386 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
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('supports datetime-local type and change callback', () => {
|
||||
const onChange = vi.fn();
|
||||
render(<DatePicker label="Schedule" type="datetime-local" value="" onChange={onChange} />);
|
||||
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('datetime-local');
|
||||
expect(input.type).toBe('text');
|
||||
|
||||
fireEvent.change(input, { target: { value: '2031-05-20T14:30' } });
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
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 date type and disabled state', () => {
|
||||
it('supports segment-by-segment editing with custom format and auto-advance', async () => {
|
||||
const onValueChange = vi.fn();
|
||||
render(
|
||||
<DatePicker
|
||||
label="Publish date"
|
||||
type="date"
|
||||
value="2031-05-20"
|
||||
onChange={() => {}}
|
||||
disabled
|
||||
<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('Publish date') as HTMLInputElement;
|
||||
expect(input.type).toBe('date');
|
||||
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="datetime-local"
|
||||
value=""
|
||||
type="date-time"
|
||||
value="2031/05/20 14:30"
|
||||
onChange={() => {}}
|
||||
rightIcon={<span data-testid="right-icon">R</span>}
|
||||
error="Invalid date"
|
||||
@@ -51,6 +396,37 @@ describe('DatePicker', () => {
|
||||
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" />,
|
||||
@@ -59,4 +435,306 @@ describe('DatePicker', () => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,4 +52,9 @@ describe('Dropdown', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,4 +89,9 @@ describe('InputField', () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,10 @@ vi.mock('@mdxeditor/editor', () => import('./mocks/mdxeditor'));
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
localStorage.clear();
|
||||
const storage = globalThis.window?.localStorage ?? globalThis.localStorage;
|
||||
if (typeof storage?.clear === 'function') {
|
||||
storage.clear();
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
@@ -34,15 +34,17 @@ export default defineConfig({
|
||||
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: 95,
|
||||
functions: 95,
|
||||
branches: 90,
|
||||
lines: 80,
|
||||
functions: 75,
|
||||
branches: 70,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
394
yarn.lock
394
yarn.lock
@@ -12,6 +12,11 @@
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@adobe/css-tools/-/css-tools-4.4.4.tgz#2856c55443d3d461693f32d2b96fb6ea92e1ffa9"
|
||||
integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==
|
||||
|
||||
"@alloc/quick-lru@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||
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"
|
||||
@@ -901,7 +906,7 @@
|
||||
glob "^13.0.1"
|
||||
react-docgen-typescript "^2.2.2"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
|
||||
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.13"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
|
||||
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
|
||||
@@ -1360,6 +1365,27 @@
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@mdxeditor/gurx/-/gurx-1.2.4.tgz#b4d16141ee1f605dde754932087de17e2d73b919"
|
||||
integrity sha512-9ZykIFYhKaXaaSPCs1cuI+FvYDegJjbKwmA4ASE/zY+hJY6EYqvoye4esiO85CjhOw9aoD/izD/CU78/egVqmg==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
version "1.2.8"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
dependencies:
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@open-draft/deferred-promise@^2.1.0":
|
||||
version "2.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd"
|
||||
@@ -2092,6 +2118,13 @@
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
|
||||
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":
|
||||
version "19.2.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
||||
@@ -2350,7 +2383,7 @@ agent-base@^7.1.0, agent-base@^7.1.2:
|
||||
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.14.0:
|
||||
version "6.14.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
||||
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
|
||||
@@ -2375,6 +2408,24 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
any-promise@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
arg@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
|
||||
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
@@ -2447,6 +2498,11 @@ bidi-js@^1.0.3:
|
||||
dependencies:
|
||||
require-from-string "^2.0.2"
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||
|
||||
brace-expansion@^5.0.2:
|
||||
version "5.0.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/brace-expansion/-/brace-expansion-5.0.3.tgz#6a9c6c268f85b53959ec527aeafe0f7300258eef"
|
||||
@@ -2454,6 +2510,13 @@ brace-expansion@^5.0.2:
|
||||
dependencies:
|
||||
balanced-match "^4.0.2"
|
||||
|
||||
braces@^3.0.3, braces@~3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.24.0:
|
||||
version "4.28.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95"
|
||||
@@ -2480,6 +2543,11 @@ bundle-name@^4.1.0:
|
||||
dependencies:
|
||||
run-applescript "^7.0.0"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||
|
||||
caniuse-lite@^1.0.30001759:
|
||||
version "1.0.30001774"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz#0e576b6f374063abcd499d202b9ba1301be29b70"
|
||||
@@ -2531,6 +2599,21 @@ check-error@^2.1.1:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5"
|
||||
integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==
|
||||
|
||||
chokidar@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
classnames@^2.3.2:
|
||||
version "2.5.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
@@ -2559,6 +2642,11 @@ codemirror@^6.0.1:
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.0.0"
|
||||
|
||||
commander@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
|
||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||
|
||||
compute-scroll-into-view@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz#2b444b2b9e4724819d2531efacb7ac094155fdf6"
|
||||
@@ -2601,6 +2689,11 @@ css.escape@^1.5.1:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
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"
|
||||
@@ -2696,11 +2789,21 @@ devlop@^1.0.0, devlop@^1.1.0:
|
||||
dependencies:
|
||||
dequal "^2.0.0"
|
||||
|
||||
didyoumean@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||
|
||||
diff@^5.0.0, diff@^5.1.0:
|
||||
version "5.2.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad"
|
||||
integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==
|
||||
|
||||
dlv@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
|
||||
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
@@ -2870,9 +2973,9 @@ eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
|
||||
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
|
||||
|
||||
eslint@^10:
|
||||
version "10.0.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint/-/eslint-10.0.1.tgz#b5c5f7706782a21590ba6451e7a30d2947273c2d"
|
||||
integrity sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==
|
||||
version "10.0.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/eslint/-/eslint-10.0.2.tgz#1009263467591810320f2e1ad52b8a750d1acbab"
|
||||
integrity sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.8.0"
|
||||
"@eslint-community/regexpp" "^4.12.2"
|
||||
@@ -2884,7 +2987,7 @@ eslint@^10:
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@humanwhocodes/retry" "^0.4.2"
|
||||
"@types/estree" "^1.0.6"
|
||||
ajv "^6.12.4"
|
||||
ajv "^6.14.0"
|
||||
cross-spawn "^7.0.6"
|
||||
debug "^4.3.2"
|
||||
escape-string-regexp "^4.0.0"
|
||||
@@ -3003,6 +3106,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-glob@^3.3.2:
|
||||
version "3.3.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
||||
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.8"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
@@ -3013,6 +3127,13 @@ fast-levenshtein@^2.0.6:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.20.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675"
|
||||
integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fault@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c"
|
||||
@@ -3032,6 +3153,13 @@ file-entry-cache@^8.0.0:
|
||||
dependencies:
|
||||
flat-cache "^4.0.0"
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
find-up@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
|
||||
@@ -3078,6 +3206,13 @@ get-nonce@^1.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
|
||||
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob-parent@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||
@@ -3194,6 +3329,13 @@ is-alphanumerical@^2.0.0:
|
||||
is-alphabetical "^2.0.0"
|
||||
is-decimal "^2.0.0"
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-core-module@^2.16.1:
|
||||
version "2.16.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
|
||||
@@ -3216,7 +3358,7 @@ is-extglob@^2.1.1:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-glob@^4.0.0, is-glob@^4.0.3:
|
||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
@@ -3235,6 +3377,11 @@ is-inside-container@^1.0.0:
|
||||
dependencies:
|
||||
is-docker "^3.0.0"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
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"
|
||||
@@ -3279,6 +3426,11 @@ istanbul-reports@^3.2.0:
|
||||
html-escaper "^2.0.0"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
|
||||
jiti@^1.21.7:
|
||||
version "1.21.7"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9"
|
||||
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"
|
||||
@@ -3380,6 +3532,16 @@ lib0@^0.2.99:
|
||||
dependencies:
|
||||
isomorphic.js "^0.2.4"
|
||||
|
||||
lilconfig@^3.1.1, lilconfig@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
|
||||
integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||
@@ -3619,6 +3781,11 @@ mdn-data@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:
|
||||
version "1.4.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
micromark-core-commonmark@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
|
||||
@@ -3976,6 +4143,14 @@ micromark@^4.0.0:
|
||||
micromark-util-symbol "^2.0.0"
|
||||
micromark-util-types "^2.0.0"
|
||||
|
||||
micromatch@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
|
||||
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
|
||||
dependencies:
|
||||
braces "^3.0.3"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mime-db@^1.52.0:
|
||||
version "1.54.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
|
||||
@@ -3986,7 +4161,14 @@ min-indent@^1.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||
|
||||
minimatch@^10.2.1, minimatch@^10.2.2:
|
||||
minimatch@^10.2.1:
|
||||
version "10.2.4"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
|
||||
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==
|
||||
dependencies:
|
||||
brace-expansion "^5.0.2"
|
||||
|
||||
minimatch@^10.2.2:
|
||||
version "10.2.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/minimatch/-/minimatch-10.2.2.tgz#361603ee323cfb83496fea2ae17cc44ea4e1f99f"
|
||||
integrity sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==
|
||||
@@ -4020,6 +4202,15 @@ ms@^2.1.3:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
mz@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
|
||||
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.11"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
@@ -4040,11 +4231,21 @@ node-releases@^2.0.27:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
|
||||
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
|
||||
|
||||
object-assign@^4.1.1:
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-hash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||
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"
|
||||
@@ -4154,12 +4355,70 @@ picocolors@1.1.1, picocolors@^1.1.1:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
picomatch@^4.0.2, picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
postcss@^8.4.49, postcss@^8.5.6:
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
|
||||
|
||||
pirates@^4.0.1:
|
||||
version "4.0.7"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22"
|
||||
integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==
|
||||
|
||||
postcss-import@^15.1.0:
|
||||
version "15.1.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
|
||||
integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
|
||||
dependencies:
|
||||
postcss-value-parser "^4.0.0"
|
||||
read-cache "^1.0.0"
|
||||
resolve "^1.1.7"
|
||||
|
||||
postcss-js@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6"
|
||||
integrity sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==
|
||||
dependencies:
|
||||
camelcase-css "^2.0.1"
|
||||
|
||||
"postcss-load-config@^4.0.2 || ^5.0 || ^6.0":
|
||||
version "6.0.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096"
|
||||
integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==
|
||||
dependencies:
|
||||
lilconfig "^3.1.1"
|
||||
|
||||
postcss-nested@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131"
|
||||
integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.1.1"
|
||||
|
||||
postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
|
||||
integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.0.0:
|
||||
version "4.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.4.47, postcss@^8.4.49, postcss@^8.5.6:
|
||||
version "8.5.6"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
@@ -4206,6 +4465,11 @@ punycode@^2.1.0, punycode@^2.3.1:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
react-devtools-inline@4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz#e032a6eb17a9977b682306f84b46e683adf4bf68"
|
||||
@@ -4315,6 +4579,20 @@ react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/react/-/react-19.2.4.tgz#438e57baa19b77cb23aab516cf635cd0579ee09a"
|
||||
integrity sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||
integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
|
||||
dependencies:
|
||||
pify "^2.3.0"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
recast@^0.23.5:
|
||||
version "0.23.11"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/recast/-/recast-0.23.11.tgz#8885570bb28cf773ba1dc600da7f502f7883f73f"
|
||||
@@ -4339,7 +4617,7 @@ require-from-string@^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.22.1, resolve@^1.22.8:
|
||||
resolve@^1.1.7, resolve@^1.22.1, resolve@^1.22.8:
|
||||
version "1.22.11"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
|
||||
integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==
|
||||
@@ -4348,6 +4626,11 @@ resolve@^1.22.1, resolve@^1.22.8:
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.1.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
|
||||
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
|
||||
|
||||
rollup@^4.43.0:
|
||||
version "4.59.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f"
|
||||
@@ -4387,6 +4670,13 @@ run-applescript@^7.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911"
|
||||
integrity sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
sade@^1.7.3:
|
||||
version "1.8.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
|
||||
@@ -4521,6 +4811,19 @@ style-mod@^4.0.0, style-mod@^4.1.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/style-mod/-/style-mod-4.1.3.tgz#6e9012255bb799bdac37e288f7671b5d71bf9f73"
|
||||
integrity sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==
|
||||
|
||||
sucrase@^3.35.0:
|
||||
version "3.35.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/sucrase/-/sucrase-3.35.1.tgz#4619ea50393fe8bd0ae5071c26abd9b2e346bfe1"
|
||||
integrity sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
commander "^4.0.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
mz "^2.7.0"
|
||||
pirates "^4.0.1"
|
||||
tinyglobby "^0.2.11"
|
||||
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"
|
||||
@@ -4543,10 +4846,47 @@ tabbable@^6.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tabbable/-/tabbable-6.4.0.tgz#36eb7a06d80b3924a22095daf45740dea3bf5581"
|
||||
integrity sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==
|
||||
|
||||
tailwindcss@^4.0.0:
|
||||
version "4.2.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tailwindcss/-/tailwindcss-4.2.1.tgz#018c4720b58baf98a6bf56b0a12aa797c6cfef1d"
|
||||
integrity sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==
|
||||
tailwindcss@^3.4.16:
|
||||
version "3.4.19"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tailwindcss/-/tailwindcss-3.4.19.tgz#af2a0a4ae302d52ebe078b6775e799e132500ee2"
|
||||
integrity sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==
|
||||
dependencies:
|
||||
"@alloc/quick-lru" "^5.2.0"
|
||||
arg "^5.0.2"
|
||||
chokidar "^3.6.0"
|
||||
didyoumean "^1.2.2"
|
||||
dlv "^1.1.3"
|
||||
fast-glob "^3.3.2"
|
||||
glob-parent "^6.0.2"
|
||||
is-glob "^4.0.3"
|
||||
jiti "^1.21.7"
|
||||
lilconfig "^3.1.3"
|
||||
micromatch "^4.0.8"
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.1.1"
|
||||
postcss "^8.4.47"
|
||||
postcss-import "^15.1.0"
|
||||
postcss-js "^4.0.1"
|
||||
postcss-load-config "^4.0.2 || ^5.0 || ^6.0"
|
||||
postcss-nested "^6.2.0"
|
||||
postcss-selector-parser "^6.1.2"
|
||||
resolve "^1.22.8"
|
||||
sucrase "^3.35.0"
|
||||
|
||||
thenify-all@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
|
||||
dependencies:
|
||||
thenify ">= 3.1.0 < 4"
|
||||
|
||||
"thenify@>= 3.1.0 < 4":
|
||||
version "3.3.1"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
|
||||
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
tiny-invariant@^1.3.3:
|
||||
version "1.3.3"
|
||||
@@ -4563,7 +4903,7 @@ tinyexec@^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.15:
|
||||
tinyglobby@^0.2.11, tinyglobby@^0.2.15:
|
||||
version "0.2.15"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
|
||||
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
|
||||
@@ -4598,6 +4938,13 @@ tldts@^7.0.5:
|
||||
dependencies:
|
||||
tldts-core "^7.0.23"
|
||||
|
||||
to-regex-range@^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"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
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"
|
||||
@@ -4622,6 +4969,11 @@ ts-dedent@^2.0.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
|
||||
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
|
||||
|
||||
ts-interface-checker@^0.1.9:
|
||||
version "0.1.13"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||
|
||||
tsconfig-paths@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c"
|
||||
@@ -4663,6 +5015,11 @@ typescript@^5.6.2:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
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"
|
||||
@@ -4758,6 +5115,11 @@ use-sync-external-store@^1.5.0:
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
|
||||
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
uvu@^0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://nexus.beatrice.wtf/repository/npm-group/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
|
||||
|
||||
Reference in New Issue
Block a user